Skip to content

feat(forensic): BlobInterpreter seam — context-aware BLOB decoding, zero decode deps#4

Merged
h4x0r merged 4 commits into
mainfrom
feat/blob-interpreter-seam
Jul 11, 2026
Merged

feat(forensic): BlobInterpreter seam — context-aware BLOB decoding, zero decode deps#4
h4x0r merged 4 commits into
mainfrom
feat/blob-interpreter-seam

Conversation

@h4x0r

@h4x0r h4x0r commented Jul 11, 2026

Copy link
Copy Markdown
Contributor

A dependency-inversion seam so library consumers can decode opaque BLOB values in schema context — WebKit Local Storage UTF-16, or (via a consumer-side adapter) the general blob-decoderwithout 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-decoder adapter 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 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>)None → empty, unchanged behaviour (non-breaking passthrough)

Step 2 — the built-in LocalStorageInterpreter, still zero new deps:
Decodes a WebKit Local Storage ItemTable.value BLOB (raw UTF-16-LE) over sqlite-core's existing decode_localstorage_value + is_local_storage_item_table. Fires only under the ItemTable schema-name prior (0.95 confidence) — that context is what makes UTF-16-LE the confident reading; an arbitrary blob returns None (the general adapter's job). Carries the lossy flag through.

Why the seam (not a hard dep)

sqlite-core/sqlite-forensic are published libraries on a deliberate low MSRV (1.80) that anything in the fleet can drop in. blob-decoder is 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 on blob-decoder directly (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

h4x0r and others added 4 commits July 12, 2026 00:12
…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>
@h4x0r h4x0r merged commit 07b5823 into main Jul 11, 2026
18 checks passed
@h4x0r h4x0r deleted the feat/blob-interpreter-seam branch July 11, 2026 16:34
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant