Metadata-Version: 2.4
Name: sqliteproof
Version: 0.1.1
Summary: Recover what survived a corrupt SQLite database: which tables are intact, how many rows are lost, what is safe to export. For 'database disk image is malformed'.
License: MIT
Project-URL: Homepage, https://github.com/OrbitalKeyAi/sqliteproof
Project-URL: Source, https://github.com/OrbitalKeyAi/sqliteproof
Project-URL: Issues, https://github.com/OrbitalKeyAi/sqliteproof/issues
Keywords: sqlite,corruption,corrupt,malformed,database-disk-image-is-malformed,integrity-check,recover,recovery,repair,salvage,forensics,database,validation,backup,restore,dba,sysadmin,devops,cli
Classifier: Development Status :: 3 - Alpha
Classifier: Intended Audience :: System Administrators
Classifier: Intended Audience :: Developers
Classifier: Topic :: Database
Classifier: Topic :: System :: Recovery Tools
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python :: 3
Classifier: Operating System :: OS Independent
Requires-Python: >=3.9
Description-Content-Type: text/markdown
License-File: LICENSE
Dynamic: license-file

# sqliteproof

**Find out which tables survived, not which pages broke.**

SQLite's own `PRAGMA integrity_check` is good at *detecting* corruption. What it gives you is this:

```
*** in database main ***
Tree 25 page 35 cell 18: Offset 57005 out of range 239..4092
database disk image is malformed
```

That's a page number. Nobody stores data by page number. What you actually need to know at
3am is **which tables can I still trust**, **how many rows did I lose**, and **is this worth
restoring from backup**.

```
$ sqliteproof app.db

app.db  —  DAMAGED

  table                        verdict         rows     lost
  ------------------------------------------------------------
  orders                       damaged      392/400        8
      breaks after row 182: database disk image is malformed
  audit_log                    intact       400/400        0
  customers                    intact       400/400        0

  1192 rows read, 8 unreadable.
  Tables marked intact above are safe to export. Restore the damaged
  ones from backup rather than trusting a partial read.
```

## Install

```bash
pip install sqliteproof
```

Python 3.9+. **No dependencies** — standard library only.

## Usage

```bash
sqliteproof app.db            # full report
sqliteproof app.db --json     # machine-readable
sqliteproof app.db --quiet    # verdict line only
```

Exit codes: **0** intact · **1** damaged · **2** unreadable or undetermined. Drops straight
into a backup script:

```bash
sqliteproof app.db --quiet || echo "corruption detected" | mail -s alert me@example.com
```

## It opens the database read-only

A tool asked to inspect a damaged file must never be able to damage it further. The
connection is opened with `mode=ro` and nothing is written to the database, ever.

Runs entirely on your machine. No network, nothing uploaded.

## It will not bluff

| verdict | meaning |
|---|---|
| `INTACT` | every table read completely and row counts match |
| `DAMAGED` | some rows are unreadable — **with the table named and the break point located** |
| `UNKNOWN` | the damage prevents a determination |

`UNKNOWN` is never dressed up as clean. A tool that reports a corrupt database as healthy
is worse than no tool.

## Limitations — read these first

**Structural, not semantic.** It verifies rows can be *read*. It cannot detect corruption
that produces valid-looking values — a flipped bit inside an integer that still parses is
invisible to it.

**Row counts come from the same damaged btree.** When `COUNT(*)` itself fails, the expected
count is unknown and the verdict degrades to `UNKNOWN` rather than guessing.

**It does not repair anything.** It tells you what survived so you can export the good
tables and restore the rest. Recovery is a different tool.

**v0.1.0.** Tested against databases built by SQLite and damaged at known page offsets:
3/3 corrupted databases localised to the correct table, 0 false alarms, 0 false-clean.
That corpus is deliberate byte corruption, which is one failure mode among several — real
corruption also arrives via truncated files, interrupted writes, and failing disks.

## Tests

```bash
python sqliteproof/tests/corrupt.py   # build the corpus
python sqliteproof/tests/score.py     # score localisation vs ground truth
```

Ground truth comes from SQLite's own page allocation: tables are built one at a time and
the pages added between "before" and "after" belong to that table. The file format isn't
my invention and the damage lands where SQLite chose to put the data.

## If you got here from an error message

These are the messages SQLite prints when a database has gone bad. If you pasted one into a
search engine and landed here, this is what each one means and what this tool does about it.

| what SQLite told you | what it means | what sqliteproof adds |
|---|---|---|
| `database disk image is malformed` | The B-tree structure is damaged somewhere. SQLite will not say where. | Which tables still read completely, which are damaged, and how many rows are gone |
| `Tree N page M cell K: Offset … out of range` | `PRAGMA integrity_check` found a specific broken page | Translates the page into the table that owns it |
| `malformed database schema` | The schema table itself is damaged | Reports UNREADABLE rather than guessing |
| `no such table` after a crash | Possibly a lost schema page | Reads what is still there, read-only, without writing |

Two things worth knowing before you do anything else:

1. **Copy the file first.** Work on the copy. Several recovery approaches write to the
   database, and a failed repair on your only copy is unrecoverable. sqliteproof itself opens
   the file read-only and never writes, which is why it is safe to run first.
2. **A clean report is not a guarantee of correct data.** It means every row was structurally
   readable. Corruption that produces valid-looking values cannot be detected this way, and
   this tool says so rather than implying otherwise.

If you need to *repair* rather than assess, SQLite's own `.recover` command in the `sqlite3`
shell is the right next step. This tool tells you whether that is worth doing and what you
stand to lose — it does not replace it.


## Related

**[statementproof](https://github.com/OrbitalKeyAi/statementproof)** — the same idea for
bank statement PDFs. Most converters hand you a CSV and leave you to trust it;
statementproof checks the extracted rows against the statement's own opening and closing
balances and names the row where the running total stops following.

## License

MIT.
