Metadata-Version: 2.4
Name: sqliteproof
Version: 0.1.0
Summary: Structural validation for SQLite databases -- tells you which tables survived, not which pages broke.
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,integrity,database,validation,backup,recovery,cli,devops
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.

## License

MIT.
