You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
docs: install three-tier validation framing; bring codes/corpora current
Make every validation claim tier-accurate across the four reader-facing docs.
validation.md — replace "Input provenance" + "Epistemic stance" with a precise
THREE-TIER model whose axis is whether the correctness check is trustworthy
(Tier 1 real third-party/real-device ground truth; Tier 2 real-engine bytes
confirmed by a derivable answer key or sqlite3/calamine, scenario chosen by us;
Tier 3 only-we-vouch, essentially just the freeblock-clobbered spilled-cell path).
State the one-line rule and correct the in-cell image thumbnail to Tier 2 (calamine
reads the embedded media back). Tier-label the Layer-2 oracle table and add rows for
journal-anomaly arm split (NIST PERSIST = Tier 1; minted hot-journal arms = Tier 2),
table_instance_risk Detector A+B, the survey FP replication, and the iOS no-panic
robustness sweep. State limitations plainly (same-schema drop+recreate undecidable;
DELETE/TRUNCATE leave no in-band residue; encrypted out of scope; FP benchmark is a
replication; Boyer-Moore inapplicable by design).
corpus-catalog.md — correct the stale §K PERSIST claim (now 100/100 via
carve_rollback_journal, not "pending 0/100"); add §N drop_recreate, §O paper_fp
FP scenarios, §P Josh Hickman iOS-17 corpus; extend the §H md5 manifest and the
classification header for the REAL-ext/REAL-device entries.
README.md — add WAL-UNCHECKPOINTED acquisition guidance; document the
table_instance_risk hint column and its honest framing; add the out-of-scope
limitations to Trust-but-verify.
index.md — anomaly-code SCHEMA-CHANGE keyed on the schema cookie; three-tier
pointer + iOS robustness in Validation; rollback-journal/hint entry points.
mkdocs build --strict passes. No Rust source touched.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Copy file name to clipboardExpand all lines: README.md
+5-2Lines changed: 5 additions & 2 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -88,6 +88,8 @@ Need a queryable database too? Add `--db` to also write `<name>.carved.db` (same
88
88
-**`recovered_unattributed` — UNKNOWN.** Dropped-table residue, or a shape matching no surviving table — recovered in full, attributed to nothing.
89
89
-**`recovered_fragments`** — the **separate** Tier-2 partial-salvage table (a distinctive cell survived but the row's identity did not), kept distinct so a fragment is never mistaken for a full row. `--no-fragments` drops it.
90
90
91
+
Each `recovered_<table>` row also carries a `table_instance_risk` column — a non-overclaiming **hint** (not an attribution tier), empty unless the residue is consistent with predating the current table instance: `rowid_exceeds_autoinc_highwater(r=…,seq=…)` (Detector A — a carved rowid above the `AUTOINCREMENT` high-water mark) or `sidecar_schema_changed(table)` (Detector B — a `-wal`/`-journal` sidecar whose prior schema differs). The hint is framed honestly: *consistent with* prior-incarnation residue, but also explainable by an `UPDATE`, a manual `sqlite_sequence` edit, or a current-instance deletion — it never asserts a predecessor or a drop+recreate, and a same-schema drop+recreate (indistinguishable from a benign `VACUUM` page move) is deliberately never flagged.
92
+
91
93
Want a queryable database, the files elsewhere, or a stream instead? Pick the option:
92
94
93
95
```console
@@ -230,7 +232,7 @@ This is one workspace (`sqlite-forensic`): two library crates following the flee
230
232
|---|:-:|---|
231
233
|`SQLITE-DELETED-RECORD-RECOVERED`| Medium | A record-shaped cell recovered from unallocated space — consistent with a deleted row not yet overwritten. Carries page / offset / rowid provenance. |
232
234
|`SQLITE-FREELIST-NONEMPTY`| Low | The database holds free pages — consistent with prior deletions (`DELETE` without `VACUUM`); those pages may retain recoverable rows. |
233
-
|`SQLITE-WAL-UNCHECKPOINTED`| Medium | A `-wal` sidecar carries committed page versions the main file does not reflect — the main file alone under-reports the true state. |
235
+
|`SQLITE-WAL-UNCHECKPOINTED`| Medium | A `-wal` sidecar carries committed page versions the main file does not reflect — the main file alone under-reports the true state. **Acquire the live `-wal` before the application terminates**: a checkpoint (e.g. on its next clean close) folds the WAL into the main file and discards the uncheckpointed deleted/superseded residue, which the post-checkpoint main file no longer contains. |
234
236
|`SQLITE-PAGECOUNT-MISMATCH`| High | The in-header page count disagrees with the count implied by file length — consistent with truncation, carving, or out-of-band modification. |
235
237
|`SQLITE-RESERVED-SPACE-NONZERO`| Low | The header reserves bytes per page — non-standard; consistent with a page-level extension such as encryption (SQLCipher/SEE) or a checksum VFS. |
236
238
|`SQLITE-JOURNAL-HOT`| High | A `-journal` with a valid header sits beside the database — consistent with an interrupted or in-progress write transaction (the main db may require rollback). |
@@ -255,7 +257,8 @@ A carver that *over*-reports is worse than useless on an evidence database — i
255
257
-**High precision, structurally — never a live-row re-read.** Our carver carves only the *complement* of the live cell extents on a page, then drops any carved record whose rowid is currently live. Across the Nemetz recall corpus it produces **0 live-re-reads** (verified against the answer key's live rows), with only a small, low-confidence **phantom** class (all-empty/NULL records the inferred carver matches on a run of zero bytes). The two over-reporting failure modes the reference oracles exhibit on no-deletion databases — re-reading live cells, and re-surfacing a stale byte-copy of a live row — our carver does not. On the 2025 SQLite false-positive survey's B-tree-rebalancing scenario, measured on **identical bytes**, this discipline yields **0 false positives** where `bring2lite` re-surfaces 13 live rows as deleted ([`docs/competitive-landscape.md`](docs/competitive-landscape.md)).
256
258
-**Strong in-page recall via freeblock reconstruction — reported honestly.** On the cleanest category (`0C`: records deleted in place, `secure_delete=0`, no overwrite, so **every** deleted row's bytes survive) the carver recovers **70 of the 84** cross-tool-scored rows (recall **0.833**), ahead of `fqlite`'s 0.798. SQLite overwrites a freed cell's first four bytes (payload-length + rowid varints, `header_len`, leading serial) with the freeblock pointer; `reconstruct_freeblock_records` rebuilds each record from its surviving serial-type tail plus a schema template derived from a live cell on the same page, with the destroyed rowid surfaced as unknown. It does so at higher precision than `fqlite` and **0 live-re-reads**.
257
259
-**Overflow-page chains: partial recovery, honestly bounded.** A deleted row whose payload spilled onto a freed overflow chain is reassembled to a full row **only when every chain page survives as a freelist leaf**; a chain page reallocated as the freelist trunk destroys the record, which is then refused from the full tier and surfaces only as a Tier-2 fragment. On the Nemetz `0E` category this reassembles the one byte-perfectly-recoverable spilled chain (verified `assert_eq!` against the answer key, substrate recall **1.000**) for an **end-to-end `0E` recall of 0.333** — a deliberately bounded capability, graded below the in-page tier, never claimed as full overflow recovery.
258
-
-**Secondary checks stay labelled as such.** The undark/fqlite differential ([`docs/validation.md`](docs/validation.md)) is **inter-tool concordance** (the oracles disagree with each other — agreement, not correctness), and the DC3 `sqlite_dissect` corpus is a **no-false-positive regression set** (its `expected_rows` are live content, not a deleted set), never a recall oracle.
260
+
-**Secondary checks stay labelled as such.** The undark/fqlite differential ([`docs/validation.md`](docs/validation.md)) is **inter-tool concordance** (the oracles disagree with each other — agreement, not correctness), and the DC3 `sqlite_dissect` corpus is a **no-false-positive regression set** (its `expected_rows` are live content, not a deleted set), never a recall oracle. The 2025 survey false-positive benchmark is a **replication** of the paper's scenario construction (the official corpus is not public yet), and the FQLite scenario-10 number is **cited from the paper**, not measured here (its WAL recovery is GUI-coupled).
261
+
-**Out of scope, stated plainly.** A **same-schema drop+recreate** is undecidable from a single snapshot and from a sidecar (indistinguishable from a benign `VACUUM` page move), so `table_instance_risk` flags only `AUTOINCREMENT` rowid-overflow and unambiguous sidecar schema changes — never the same-schema case. **DELETE-mode** (the `-journal` is unlinked) and **TRUNCATE-mode** (it is zeroed) rollback journals leave no in-band residue (a disk-carving-layer concern). **Encrypted databases** (SQLCipher / SEE) are out of scope. The carver is **structural** (b-tree / freelist / journal layout), so a Boyer-Moore signature scan is inapplicable by design.
259
262
260
263
Carved records remain **confidence-graded observations** ("consistent with a deleted row"), never a verdict. The honest summary: a strict precision discipline confirmed against independent ground truth, and a documented in-page recall gap — not a claim of perfect recall or proof of correctness.
0 commit comments