feat(forensic): BlobInterpreter seam — context-aware BLOB decoding, zero decode deps#4
Merged
Merged
Conversation
…through)
Dependency-inversion seam so library consumers can opt into context-aware BLOB
decoding without the reader library gaining any decode dependency or MSRV cost.
Step 1 defines the contract and proves it:
- trait BlobInterpreter { interpret(&self, bytes, ctx) -> Option<Interpretation> }
- BlobContext { table, column } — the schema context the library owns
- Interpretation { text, kind, lossy, confidence } — lossy is a struct field
(secure by design: a consumer can't render a lossy decode as faithful)
- interpret_values(&[Value], &BlobContext, Option<&dyn BlobInterpreter>)
-> Vec<(usize, Interpretation)>: applies the interpreter to each BLOB value;
None → empty (unchanged behaviour).
Scoped to &[Value] (not CarvedRecord, whose non_exhaustive RecoverySource blocks
external construction). Fails: the interpret module does not exist yet.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ssthrough)
New forensic::interpret module (zero decode deps, MSRV 1.80 untouched):
- trait BlobInterpreter { interpret(&self, bytes, &BlobContext) -> Option<Interpretation> }
- BlobContext { table, column } — schema context as a decoding prior
- Interpretation { text, kind, lossy, confidence } — lossy is a struct field
(secure by design; a consumer can't render a lossy decode as faithful)
- interpret_values(&[Value], &BlobContext, Option<&dyn BlobInterpreter>)
-> Vec<(usize, Interpretation)>: applies the interpreter per BLOB; None →
empty (unchanged, non-breaking).
Dependency inversion: the library owns the contract + context; a consumer supplies
the decoder (blob-decoder adapter, or the built-in localstorage decoder in later
steps). Workspace clippy/fmt/rustdoc clean; function coverage 100%.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The built-in (dependency-free) BlobInterpreter for WebKit Local Storage: an ItemTable.value BLOB is raw UTF-16-LE, so with the ItemTable schema-name context it decodes with high confidence (kind "utf-16", lossy flagged on an odd-length blob). Without that context it recognises nothing (an arbitrary blob is the blob-decoder adapter's job). Fails: LocalStorageInterpreter does not exist yet. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
LocalStorageInterpreter implements BlobInterpreter over sqlite-core's decode_localstorage_value + is_local_storage_item_table (no new deps). It fires ONLY under the ItemTable schema-name prior — that context is what makes UTF-16-LE the confident reading (0.95); an arbitrary blob returns None (the general blob-decoder adapter's job). Carries the lossy flag through verbatim, so an odd-length blob is never presented as a faithful decode. The "schema context lifts a structural-Low reading to High" seam, concrete. Workspace clippy/fmt/rustdoc clean; function coverage 100%. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
A dependency-inversion seam so library consumers can decode opaque
BLOBvalues in schema context — WebKit Local Storage UTF-16, or (via a consumer-side adapter) the generalblob-decoder— without the reader library taking on any decode dependency or MSRV cost. The library owns the contract + schema context; a consumer supplies the decoder.This is the library half of the plan (steps 1–2 of 5). Steps 3–5 (CLI JSONL surfacing, a
blob-decoderadapter crate, retiring the §4.5 hand-rolled magic-typer) are consumer-side follow-ups.What's here
Step 1 — the contract (
forensic::interpret), zero deps:trait BlobInterpreter { interpret(&self, bytes, &BlobContext) -> Option<Interpretation> }BlobContext { table, column }— the schema context the library ownsInterpretation { text, kind, lossy, confidence }—lossyis a struct field (secure by design: a consumer can't render a lossy decode as faithful)interpret_values(&[Value], &BlobContext, Option<&dyn BlobInterpreter>)—None→ empty, unchanged behaviour (non-breaking passthrough)Step 2 — the built-in
LocalStorageInterpreter, still zero new deps:Decodes a WebKit Local Storage
ItemTable.valueBLOB (raw UTF-16-LE) over sqlite-core's existingdecode_localstorage_value+is_local_storage_item_table. Fires only under theItemTableschema-name prior (0.95 confidence) — that context is what makes UTF-16-LE the confident reading; an arbitrary blob returnsNone(the general adapter's job). Carries thelossyflag through.Why the seam (not a hard dep)
sqlite-core/sqlite-forensicare published libraries on a deliberate low MSRV (1.80) that anything in the fleet can drop in.blob-decoderis 1.88 and pulls flate2/plist/serde_json/snap — forcing it on every consumer would fight that. With this seam, issen / sqlite4n6 / browser-forensic each depend onblob-decoderdirectly (their own MSRV budget) and hand it in; the reader you can drop into anything never carries the decode deps.Tests
Strict TDD, separate RED→GREEN commits per step; each RED verified failing first. Function coverage 100%; clippy
-D warnings, fmt, rustdoc all clean.🤖 Generated with Claude Code