backdraft 0.4.1__tar.gz → 0.5.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {backdraft-0.4.1 → backdraft-0.5.0}/.claude-plugin/plugin.json +1 -1
- {backdraft-0.4.1 → backdraft-0.5.0}/DESIGN.md +1 -0
- {backdraft-0.4.1 → backdraft-0.5.0}/PKG-INFO +6 -4
- {backdraft-0.4.1 → backdraft-0.5.0}/README.md +5 -3
- backdraft-0.5.0/ROADMAP.md +177 -0
- {backdraft-0.4.1 → backdraft-0.5.0}/SPEC.md +5 -3
- {backdraft-0.4.1 → backdraft-0.5.0}/demo/walkthrough.md +20 -21
- {backdraft-0.4.1 → backdraft-0.5.0}/pyproject.toml +1 -1
- {backdraft-0.4.1 → backdraft-0.5.0}/site/docs.html +20 -7
- {backdraft-0.4.1 → backdraft-0.5.0}/site/llms.txt +7 -0
- backdraft-0.5.0/site/privacy.html +126 -0
- {backdraft-0.4.1 → backdraft-0.5.0}/skills/backdraft/SKILL.md +12 -5
- {backdraft-0.4.1 → backdraft-0.5.0}/src/backdraft/bind/cli.py +25 -3
- {backdraft-0.4.1 → backdraft-0.5.0}/src/backdraft/cli.py +78 -66
- backdraft-0.5.0/src/backdraft/extract/snapshots.py +170 -0
- {backdraft-0.4.1 → backdraft-0.5.0}/src/backdraft/extract/vlm.py +20 -18
- {backdraft-0.4.1 → backdraft-0.5.0}/src/backdraft/gate/reader.py +23 -3
- {backdraft-0.4.1 → backdraft-0.5.0}/src/backdraft/gate/searcher.py +2 -2
- {backdraft-0.4.1 → backdraft-0.5.0}/tests/gate/test_gate_cli.py +13 -3
- {backdraft-0.4.1 → backdraft-0.5.0}/tests/gate/test_gate_reader.py +17 -0
- {backdraft-0.4.1 → backdraft-0.5.0}/tests/test_bind_cli.py +26 -0
- {backdraft-0.4.1 → backdraft-0.5.0}/tests/test_cli.py +24 -0
- {backdraft-0.4.1 → backdraft-0.5.0}/tests/test_extract.py +20 -24
- backdraft-0.5.0/tests/test_snapshots.py +509 -0
- {backdraft-0.4.1 → backdraft-0.5.0}/uv.lock +1 -1
- backdraft-0.4.1/ROADMAP.md +0 -63
- {backdraft-0.4.1 → backdraft-0.5.0}/.claude-plugin/marketplace.json +0 -0
- {backdraft-0.4.1 → backdraft-0.5.0}/.github/workflows/ci.yml +0 -0
- {backdraft-0.4.1 → backdraft-0.5.0}/.github/workflows/release.yml +0 -0
- {backdraft-0.4.1 → backdraft-0.5.0}/.github/workflows/site.yml +0 -0
- {backdraft-0.4.1 → backdraft-0.5.0}/.gitignore +0 -0
- {backdraft-0.4.1 → backdraft-0.5.0}/.python-version +0 -0
- {backdraft-0.4.1 → backdraft-0.5.0}/LICENSE +0 -0
- {backdraft-0.4.1 → backdraft-0.5.0}/assets/backdraft-mark.svg +0 -0
- {backdraft-0.4.1 → backdraft-0.5.0}/demo/generate_sources.py +0 -0
- {backdraft-0.4.1 → backdraft-0.5.0}/demo/memo.backdraft.html +0 -0
- {backdraft-0.4.1 → backdraft-0.5.0}/demo/memo.md +0 -0
- {backdraft-0.4.1 → backdraft-0.5.0}/demo/sources/t12-summary.pdf +0 -0
- {backdraft-0.4.1 → backdraft-0.5.0}/demo/sources/underwriting-model.xlsx +0 -0
- {backdraft-0.4.1 → backdraft-0.5.0}/site/demo.html +0 -0
- {backdraft-0.4.1 → backdraft-0.5.0}/site/index.html +0 -0
- {backdraft-0.4.1 → backdraft-0.5.0}/skills/backdraft-artifact/SKILL.md +0 -0
- {backdraft-0.4.1 → backdraft-0.5.0}/skills/backdraft-backfill/SKILL.md +0 -0
- {backdraft-0.4.1 → backdraft-0.5.0}/spec/artifact.md +0 -0
- {backdraft-0.4.1 → backdraft-0.5.0}/spec/chunking.md +0 -0
- {backdraft-0.4.1 → backdraft-0.5.0}/spec/tokens.md +0 -0
- {backdraft-0.4.1 → backdraft-0.5.0}/src/backdraft/__init__.py +0 -0
- {backdraft-0.4.1 → backdraft-0.5.0}/src/backdraft/__main__.py +0 -0
- {backdraft-0.4.1 → backdraft-0.5.0}/src/backdraft/bind/__init__.py +0 -0
- {backdraft-0.4.1 → backdraft-0.5.0}/src/backdraft/bind/binder.py +0 -0
- {backdraft-0.4.1 → backdraft-0.5.0}/src/backdraft/bind/evidence.py +0 -0
- {backdraft-0.4.1 → backdraft-0.5.0}/src/backdraft/bind/verify/__init__.py +0 -0
- {backdraft-0.4.1 → backdraft-0.5.0}/src/backdraft/bind/verify/base.py +0 -0
- {backdraft-0.4.1 → backdraft-0.5.0}/src/backdraft/bind/verify/entail.py +0 -0
- {backdraft-0.4.1 → backdraft-0.5.0}/src/backdraft/bind/verify/overlap.py +0 -0
- {backdraft-0.4.1 → backdraft-0.5.0}/src/backdraft/bind/verify/recompute.py +0 -0
- {backdraft-0.4.1 → backdraft-0.5.0}/src/backdraft/bind/verify/value_trace.py +0 -0
- {backdraft-0.4.1 → backdraft-0.5.0}/src/backdraft/cli_context.py +0 -0
- {backdraft-0.4.1 → backdraft-0.5.0}/src/backdraft/credentials.py +0 -0
- {backdraft-0.4.1 → backdraft-0.5.0}/src/backdraft/extract/__init__.py +0 -0
- {backdraft-0.4.1 → backdraft-0.5.0}/src/backdraft/extract/base.py +0 -0
- {backdraft-0.4.1 → backdraft-0.5.0}/src/backdraft/extract/csv.py +0 -0
- {backdraft-0.4.1 → backdraft-0.5.0}/src/backdraft/extract/docx.py +0 -0
- {backdraft-0.4.1 → backdraft-0.5.0}/src/backdraft/extract/image.py +0 -0
- {backdraft-0.4.1 → backdraft-0.5.0}/src/backdraft/extract/pdf_text.py +0 -0
- {backdraft-0.4.1 → backdraft-0.5.0}/src/backdraft/extract/pptx.py +0 -0
- {backdraft-0.4.1 → backdraft-0.5.0}/src/backdraft/extract/sheet.py +0 -0
- {backdraft-0.4.1 → backdraft-0.5.0}/src/backdraft/extract/text.py +0 -0
- {backdraft-0.4.1 → backdraft-0.5.0}/src/backdraft/extract/vlm_settings.py +0 -0
- {backdraft-0.4.1 → backdraft-0.5.0}/src/backdraft/extract/xls.py +0 -0
- {backdraft-0.4.1 → backdraft-0.5.0}/src/backdraft/extract/xlsx.py +0 -0
- {backdraft-0.4.1 → backdraft-0.5.0}/src/backdraft/gate/__init__.py +0 -0
- {backdraft-0.4.1 → backdraft-0.5.0}/src/backdraft/gate/cli.py +0 -0
- {backdraft-0.4.1 → backdraft-0.5.0}/src/backdraft/kernel/__init__.py +0 -0
- {backdraft-0.4.1 → backdraft-0.5.0}/src/backdraft/kernel/artifact.py +0 -0
- {backdraft-0.4.1 → backdraft-0.5.0}/src/backdraft/kernel/chunking.py +0 -0
- {backdraft-0.4.1 → backdraft-0.5.0}/src/backdraft/kernel/claims.py +0 -0
- {backdraft-0.4.1 → backdraft-0.5.0}/src/backdraft/kernel/errors.py +0 -0
- {backdraft-0.4.1 → backdraft-0.5.0}/src/backdraft/kernel/hashing.py +0 -0
- {backdraft-0.4.1 → backdraft-0.5.0}/src/backdraft/kernel/model.py +0 -0
- {backdraft-0.4.1 → backdraft-0.5.0}/src/backdraft/kernel/tokens.py +0 -0
- {backdraft-0.4.1 → backdraft-0.5.0}/src/backdraft/registry/__init__.py +0 -0
- {backdraft-0.4.1 → backdraft-0.5.0}/src/backdraft/registry/ledger.py +0 -0
- {backdraft-0.4.1 → backdraft-0.5.0}/src/backdraft/registry/schema.sql +0 -0
- {backdraft-0.4.1 → backdraft-0.5.0}/src/backdraft/registry/store.py +0 -0
- {backdraft-0.4.1 → backdraft-0.5.0}/src/backdraft/render/__init__.py +0 -0
- {backdraft-0.4.1 → backdraft-0.5.0}/src/backdraft/render/_text.py +0 -0
- {backdraft-0.4.1 → backdraft-0.5.0}/src/backdraft/render/cli.py +0 -0
- {backdraft-0.4.1 → backdraft-0.5.0}/src/backdraft/render/footnotes.py +0 -0
- {backdraft-0.4.1 → backdraft-0.5.0}/src/backdraft/render/html/__init__.py +0 -0
- {backdraft-0.4.1 → backdraft-0.5.0}/src/backdraft/render/html/assets.py +0 -0
- {backdraft-0.4.1 → backdraft-0.5.0}/src/backdraft/render/html/components.py +0 -0
- {backdraft-0.4.1 → backdraft-0.5.0}/src/backdraft/render/html/fmt.py +0 -0
- {backdraft-0.4.1 → backdraft-0.5.0}/src/backdraft/render/html/fmt_vectors.py +0 -0
- {backdraft-0.4.1 → backdraft-0.5.0}/src/backdraft/render/html/page.py +0 -0
- {backdraft-0.4.1 → backdraft-0.5.0}/src/backdraft/render/html/text.py +0 -0
- {backdraft-0.4.1 → backdraft-0.5.0}/src/backdraft/render/markdown.py +0 -0
- {backdraft-0.4.1 → backdraft-0.5.0}/src/backdraft/render/placement.py +0 -0
- {backdraft-0.4.1 → backdraft-0.5.0}/src/backdraft/render/sidecar.py +0 -0
- {backdraft-0.4.1 → backdraft-0.5.0}/tests/conftest.py +0 -0
- {backdraft-0.4.1 → backdraft-0.5.0}/tests/conftest_registry.py +0 -0
- {backdraft-0.4.1 → backdraft-0.5.0}/tests/conftest_render.py +0 -0
- {backdraft-0.4.1 → backdraft-0.5.0}/tests/fakes.py +0 -0
- {backdraft-0.4.1 → backdraft-0.5.0}/tests/gate/conftest.py +0 -0
- {backdraft-0.4.1 → backdraft-0.5.0}/tests/gate/fake_registry.py +0 -0
- {backdraft-0.4.1 → backdraft-0.5.0}/tests/gate/test_gate_ledger.py +0 -0
- {backdraft-0.4.1 → backdraft-0.5.0}/tests/gate/test_gate_searcher.py +0 -0
- {backdraft-0.4.1 → backdraft-0.5.0}/tests/gate/test_reader_cells.py +0 -0
- {backdraft-0.4.1 → backdraft-0.5.0}/tests/golden/bind/backfill.bound.md +0 -0
- {backdraft-0.4.1 → backdraft-0.5.0}/tests/golden/bind/frontwalk.bound.md +0 -0
- {backdraft-0.4.1 → backdraft-0.5.0}/tests/golden/chunking/long_tail.json +0 -0
- {backdraft-0.4.1 → backdraft-0.5.0}/tests/golden/chunking/long_tail.txt +0 -0
- {backdraft-0.4.1 → backdraft-0.5.0}/tests/golden/chunking/many_small.json +0 -0
- {backdraft-0.4.1 → backdraft-0.5.0}/tests/golden/chunking/many_small.txt +0 -0
- {backdraft-0.4.1 → backdraft-0.5.0}/tests/golden/chunking/mixed.json +0 -0
- {backdraft-0.4.1 → backdraft-0.5.0}/tests/golden/chunking/mixed.txt +0 -0
- {backdraft-0.4.1 → backdraft-0.5.0}/tests/golden/chunking/one_giant.json +0 -0
- {backdraft-0.4.1 → backdraft-0.5.0}/tests/golden/chunking/one_giant.txt +0 -0
- {backdraft-0.4.1 → backdraft-0.5.0}/tests/golden/chunking/short_page.json +0 -0
- {backdraft-0.4.1 → backdraft-0.5.0}/tests/golden/chunking/short_page.txt +0 -0
- {backdraft-0.4.1 → backdraft-0.5.0}/tests/golden/chunking/unsplittable.json +0 -0
- {backdraft-0.4.1 → backdraft-0.5.0}/tests/golden/chunking/unsplittable.txt +0 -0
- {backdraft-0.4.1 → backdraft-0.5.0}/tests/golden/render/backfill.footnotes.md +0 -0
- {backdraft-0.4.1 → backdraft-0.5.0}/tests/golden/render/backfill.sidecar.json +0 -0
- {backdraft-0.4.1 → backdraft-0.5.0}/tests/golden/render/demo.footnotes.md +0 -0
- {backdraft-0.4.1 → backdraft-0.5.0}/tests/golden/render/demo.sidecar.json +0 -0
- {backdraft-0.4.1 → backdraft-0.5.0}/tests/golden_util.py +0 -0
- {backdraft-0.4.1 → backdraft-0.5.0}/tests/test_bind_binder.py +0 -0
- {backdraft-0.4.1 → backdraft-0.5.0}/tests/test_bind_evidence.py +0 -0
- {backdraft-0.4.1 → backdraft-0.5.0}/tests/test_bind_verify.py +0 -0
- {backdraft-0.4.1 → backdraft-0.5.0}/tests/test_chunking.py +0 -0
- {backdraft-0.4.1 → backdraft-0.5.0}/tests/test_claims.py +0 -0
- {backdraft-0.4.1 → backdraft-0.5.0}/tests/test_credentials.py +0 -0
- {backdraft-0.4.1 → backdraft-0.5.0}/tests/test_fmt_parity.py +0 -0
- {backdraft-0.4.1 → backdraft-0.5.0}/tests/test_footnotes.py +0 -0
- {backdraft-0.4.1 → backdraft-0.5.0}/tests/test_gate_integration.py +0 -0
- {backdraft-0.4.1 → backdraft-0.5.0}/tests/test_hashing.py +0 -0
- {backdraft-0.4.1 → backdraft-0.5.0}/tests/test_html.py +0 -0
- {backdraft-0.4.1 → backdraft-0.5.0}/tests/test_invariants.py +0 -0
- {backdraft-0.4.1 → backdraft-0.5.0}/tests/test_model.py +0 -0
- {backdraft-0.4.1 → backdraft-0.5.0}/tests/test_placement.py +0 -0
- {backdraft-0.4.1 → backdraft-0.5.0}/tests/test_records_location.py +0 -0
- {backdraft-0.4.1 → backdraft-0.5.0}/tests/test_registry.py +0 -0
- {backdraft-0.4.1 → backdraft-0.5.0}/tests/test_reingest.py +0 -0
- {backdraft-0.4.1 → backdraft-0.5.0}/tests/test_render_cli.py +0 -0
- {backdraft-0.4.1 → backdraft-0.5.0}/tests/test_render_markdown.py +0 -0
- {backdraft-0.4.1 → backdraft-0.5.0}/tests/test_sidecar.py +0 -0
- {backdraft-0.4.1 → backdraft-0.5.0}/tests/test_snapshot_settings.py +0 -0
- {backdraft-0.4.1 → backdraft-0.5.0}/tests/test_tokens.py +0 -0
- {backdraft-0.4.1 → backdraft-0.5.0}/tests/test_value_trace.py +0 -0
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "backdraft",
|
|
3
3
|
"displayName": "Backdraft",
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.5.0",
|
|
5
5
|
"description": "Drop-in provenance for factual claims: one click from any claim to the evidence behind it. Ships the backdraft writing, backfill, and artifact-reading skills; the CLI installs separately (uv tool install backdraft).",
|
|
6
6
|
"author": {
|
|
7
7
|
"name": "Spencer Braun",
|
|
@@ -134,6 +134,7 @@ Self-contained HTML (plus the machine-readable sidecar): **quotes + receipts + e
|
|
|
134
134
|
| 2026-07-29 | **Office formats extract natively, never through conversion.** DOCX and PPTX are read straight from their XML (python-docx / python-pptx, core deps). Converting to PDF was rejected on principle: conversion is rendering, so it needs a layout engine (LibreOffice, MS Office, or a cloud API — a 700MB dependency, a platform lock, or documents leaving the machine), and converter-dependent pagination would break content-addressed locator stability. DOCX pages are heading sections: the split level is the smallest outline level present among {Heading 1, Heading 2}; preamble is section 1; no headings → one section — locators stay `pN.cM`, no grammar change. PPTX pages are slides (slide N is slide N in every renderer). The text floor deliberately misses slide visuals; the ingest note says so and names the path that captures them — the user exporting the deck to PDF with the real renderer they already have, ingested through the vision extractor — so a calling agent can relay it. |
|
|
135
135
|
| 2026-07-29 | **The vision extractor ships by default (supersedes "[vlm] as extra").** `openai` + `pdf2image` moved into core dependencies; `[vlm]` remains as an empty compat alias. Rationale: the VLM path is the recommended path for real PDFs, and an extra made the recommended path opt-in. The consent boundary does not move — installing deps was never consent; the backdraft-scoped `BACKDRAFT_VLM_API_KEY` is, and ambient provider keys stay unread. There is no slim install: pip extras can only add, never subtract; a separate core distribution is possible later if anyone asks. poppler stays a runtime capability check. |
|
|
136
136
|
| 2026-07-29 | **Renderer decomposed; Python is the formatting authority.** `render/html.py` (1,300 lines) became the `render/html/` package (fmt, text, components, assets, page), byte-identical at the seam. The deliberate `fmt_cell` (Python) / `fmtCell` (JS) duplication is now pinned by one shared vector table driving both tests, with the JS run under node when available. The JS was found diverging on exact rounding ties (`toFixed` rounds half-up; Python rounds half-to-even on the double's value) and was rewritten to round exactly — Python defines correct, JS matches it. The artifact emits minified CSS/JS and unindented markup; the `$legend` and sidecar JSON stay readable in view-source (spec constraint). Snapshot budget knobs `BACKDRAFT_SNAPSHOT_QUALITY`/`BACKDRAFT_SNAPSHOT_MAX_HEIGHT` tune page-image weight at ingest — display only, tokens never derive from pixels. |
|
|
137
|
+
| 2026-07-30 | **Every PDF ingest captures page images, not just the VLM path's.** The text-layer route stored none, so the keyless path produced visually poorer artifacts for no principled reason — the pages render locally through poppler, no model calls, and the capability already existed as the `snapshot-pages` backfill. The capture is a step *after* extraction, owned by `cli.py` over `extract/snapshots.py`, deliberately **not** inside `pdf-text`: an extractor whose output varied with whether poppler happened to be installed would not be `deterministic = True`, and the same bytes must extract identically on every machine. Because snapshots only ever write `page_images` — a table no token, chunk, or receipt path reads — capture is safe to make best-effort: no poppler means ingest still exits 0, with a one-line note naming `snapshot-pages` as the backfill, rather than failing over a display concern. Tradeoff accepted: PDF ingest is now slower by a local render per page (one poppler call per page rather than one per document, to bound peak memory on long PDFs), and a library caller using `Registry.ingest` directly gets no snapshots — the policy lives at the CLI, which is the substrate that has a user to notify. |
|
|
137
138
|
|
|
138
139
|
## Open
|
|
139
140
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: backdraft
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.5.0
|
|
4
4
|
Summary: Drop-in provenance for factual claims: one click from any claim to the evidence behind it.
|
|
5
5
|
Project-URL: Homepage, https://backdraft.dev
|
|
6
6
|
Project-URL: Documentation, https://backdraft.dev/docs.html
|
|
@@ -61,6 +61,7 @@ Markdown.
|
|
|
61
61
|
$ backdraft init
|
|
62
62
|
registry: /…/demo/.backdraft
|
|
63
63
|
documents: 0
|
|
64
|
+
… plus where settings live and what to do next
|
|
64
65
|
|
|
65
66
|
$ backdraft ingest sources/t12-summary.pdf sources/underwriting-model.xlsx
|
|
66
67
|
t12-summary t12-summary.pdf pdf 3 pages
|
|
@@ -130,7 +131,7 @@ $ backdraft bind memo.md --session s-bridgeview --check value-trace,overlap
|
|
|
130
131
|
bound 15 claim(s), 16 citation(s) [frontwalk]
|
|
131
132
|
resolved: 15
|
|
132
133
|
unresolved: 1
|
|
133
|
-
overlap:
|
|
134
|
+
overlap: pass 11, skip 4
|
|
134
135
|
value-trace: pass 15
|
|
135
136
|
! unresolved: bd:t12-summary:p4.c1:1a2b
|
|
136
137
|
wrote .backdraft/records/memo.backdraft.json
|
|
@@ -209,8 +210,9 @@ explicit, backdraft-scoped consent: `BACKDRAFT_VLM_API_KEY` (env or
|
|
|
209
210
|
key in the environment is not consent to send documents to its provider. The
|
|
210
211
|
default provider is OpenRouter running Gemini 3.1 Flash Lite; direct OpenAI is
|
|
211
212
|
base_url + model, set explicitly. Without a scoped key, `auto` falls back to
|
|
212
|
-
the keyless text layer and says so
|
|
213
|
-
installed separately
|
|
213
|
+
the keyless text layer and says so — still storing each page's image, which it
|
|
214
|
+
renders locally. PDF page rendering needs poppler, installed separately; without
|
|
215
|
+
it ingest succeeds without the images and names `snapshot-pages` as the backfill.
|
|
214
216
|
|
|
215
217
|
Two extras:
|
|
216
218
|
|
|
@@ -36,6 +36,7 @@ Markdown.
|
|
|
36
36
|
$ backdraft init
|
|
37
37
|
registry: /…/demo/.backdraft
|
|
38
38
|
documents: 0
|
|
39
|
+
… plus where settings live and what to do next
|
|
39
40
|
|
|
40
41
|
$ backdraft ingest sources/t12-summary.pdf sources/underwriting-model.xlsx
|
|
41
42
|
t12-summary t12-summary.pdf pdf 3 pages
|
|
@@ -105,7 +106,7 @@ $ backdraft bind memo.md --session s-bridgeview --check value-trace,overlap
|
|
|
105
106
|
bound 15 claim(s), 16 citation(s) [frontwalk]
|
|
106
107
|
resolved: 15
|
|
107
108
|
unresolved: 1
|
|
108
|
-
overlap:
|
|
109
|
+
overlap: pass 11, skip 4
|
|
109
110
|
value-trace: pass 15
|
|
110
111
|
! unresolved: bd:t12-summary:p4.c1:1a2b
|
|
111
112
|
wrote .backdraft/records/memo.backdraft.json
|
|
@@ -184,8 +185,9 @@ explicit, backdraft-scoped consent: `BACKDRAFT_VLM_API_KEY` (env or
|
|
|
184
185
|
key in the environment is not consent to send documents to its provider. The
|
|
185
186
|
default provider is OpenRouter running Gemini 3.1 Flash Lite; direct OpenAI is
|
|
186
187
|
base_url + model, set explicitly. Without a scoped key, `auto` falls back to
|
|
187
|
-
the keyless text layer and says so
|
|
188
|
-
installed separately
|
|
188
|
+
the keyless text layer and says so — still storing each page's image, which it
|
|
189
|
+
renders locally. PDF page rendering needs poppler, installed separately; without
|
|
190
|
+
it ingest succeeds without the images and names `snapshot-pages` as the backfill.
|
|
189
191
|
|
|
190
192
|
Two extras:
|
|
191
193
|
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
# Roadmap
|
|
2
|
+
|
|
3
|
+
Sequenced intent. DESIGN.md records what was *decided*; this file records what
|
|
4
|
+
is *queued*, so tabling something is an act of writing it down here rather than
|
|
5
|
+
losing it. Items graduate by getting built and deleted.
|
|
6
|
+
|
|
7
|
+
## How the queue runs
|
|
8
|
+
|
|
9
|
+
The Now section is an ordered queue written for an implementing agent with no
|
|
10
|
+
session context: each item carries its intent, its shape, and its acceptance
|
|
11
|
+
test. The working agreement is one item at a time, top first, shipped complete
|
|
12
|
+
— code, tests, docs, and a DESIGN.md decision row where the item calls for one
|
|
13
|
+
— then deleted from this file in the same commit. Releases cut Friday, so an
|
|
14
|
+
item that lands mid-week waits on PyPI until the release; nothing ships
|
|
15
|
+
half-done to make a release. An item that turns out bigger than its sizing
|
|
16
|
+
gets split here, not stretched silently.
|
|
17
|
+
|
|
18
|
+
The cadence is daily and the runner is an agent: one item each morning Monday
|
|
19
|
+
through Thursday, landing as a direct commit to `main` — this repo does not
|
|
20
|
+
use pull requests, so a week of commits is the only review surface there is.
|
|
21
|
+
|
|
22
|
+
Friday builds nothing new. It reads that week's diff instead — duplication,
|
|
23
|
+
drift from the repo's own idioms, branches that landed untested, help text and
|
|
24
|
+
error messages that confuse, commands the docs describe wrongly — and lands
|
|
25
|
+
the fixes. Then the release, and only if something landed since the last tag:
|
|
26
|
+
the version moves in both `pyproject.toml` and `.claude-plugin/plugin.json`,
|
|
27
|
+
and publishing the GitHub release is what ships to PyPI. Then this section is
|
|
28
|
+
refilled with five items for the week ahead, drawn against the core — the four
|
|
29
|
+
verbs, the registry, the grammar, the artifact — and against where an agent
|
|
30
|
+
using backdraft actually stumbles. What the maintenance pass just found is the
|
|
31
|
+
best evidence available for what those five should be.
|
|
32
|
+
|
|
33
|
+
## Now
|
|
34
|
+
|
|
35
|
+
### 1. Source card: contents resize with the card
|
|
36
|
+
|
|
37
|
+
**Intent.** The artifact's source card is user-resizable (`resize:vertical`,
|
|
38
|
+
`max-height:82vh` — assets.py) but its inner blocks hold their own fixed caps
|
|
39
|
+
— the quote scroller is pinned at `15rem`, and the sheet grid and page image
|
|
40
|
+
size themselves — so dragging the card taller yields whitespace, not more
|
|
41
|
+
evidence. Resizing should mean seeing more.
|
|
42
|
+
|
|
43
|
+
**Shape.** Make the card a flex column whose scrollable evidence region
|
|
44
|
+
(quote / drift / sheet / page image) flexes to fill the card's height, with the
|
|
45
|
+
header, source selector, and tabs as fixed rows. Audit every fixed
|
|
46
|
+
`max-height` inside `.card` and convert to flex-driven sizing. The mobile
|
|
47
|
+
card keeps `resize:none` and its current behavior. Python is the formatting
|
|
48
|
+
authority and this is CSS/JS only, so no fmt-parity surface is touched; the
|
|
49
|
+
minified asset budget should move only trivially.
|
|
50
|
+
|
|
51
|
+
**Acceptance.** Dragging the card taller visibly grows the quote, sheet, and
|
|
52
|
+
page-image viewports; dragging it short reintroduces inner scrolling rather
|
|
53
|
+
than clipping the header or tabs. Verified against a real artifact (the demo)
|
|
54
|
+
across the three evidence kinds, plus the mobile breakpoint. Node-side DOM
|
|
55
|
+
test if cheap; otherwise the existing render tests plus a manual screenshot
|
|
56
|
+
pass recorded in the PR.
|
|
57
|
+
|
|
58
|
+
**Size.** One day.
|
|
59
|
+
|
|
60
|
+
### 2. Theming: sticky user preferences, drop-in themes
|
|
61
|
+
|
|
62
|
+
**Intent.** The artifact's look is currently one hardcoded design. Let a user
|
|
63
|
+
set a theme once and have every artifact they render honor it, and let a theme
|
|
64
|
+
be a small file someone can drop in — fonts, colors, heading treatment at
|
|
65
|
+
minimum.
|
|
66
|
+
|
|
67
|
+
**Shape.** Two parts, in order:
|
|
68
|
+
|
|
69
|
+
- *Decision row first.* A theme is a named set of overrides for the CSS custom
|
|
70
|
+
properties the stylesheet already exposes (`--paper`, `--ink`, `--sel`, the
|
|
71
|
+
font stacks) plus a bounded set of typographic choices (heading family/case,
|
|
72
|
+
maybe rule weight). A theme may not change layout, structure, or any
|
|
73
|
+
verification affordance — display only, tokens and records untouched.
|
|
74
|
+
Settle the file format (a flat TOML of variable names, or a raw CSS-variables
|
|
75
|
+
block) and write the DESIGN.md row before code.
|
|
76
|
+
- *Then the loader.* Precedence: `render --theme <file-or-name>` >
|
|
77
|
+
project `.backdraft/theme.*` > user-wide config (XDG,
|
|
78
|
+
`~/.config/backdraft/theme.*`) > built-in default. The user-wide file is
|
|
79
|
+
what makes preferences stick across projects. Bundled themes (the default
|
|
80
|
+
plus one or two alternates) prove the format; the resolved theme is baked
|
|
81
|
+
into the artifact at render time so the file stays self-contained.
|
|
82
|
+
|
|
83
|
+
**Acceptance.** Rendering with no config is byte-stable against today's
|
|
84
|
+
output. A theme file in the XDG location changes fonts/colors/headers on every
|
|
85
|
+
subsequent render in any project without flags; `--theme` overrides it; a
|
|
86
|
+
malformed theme fails with a clear message, never a half-styled artifact.
|
|
87
|
+
Docs page gains a short theming section with a sample theme file.
|
|
88
|
+
|
|
89
|
+
**Size.** Two days — decision row and variable audit, then loader, bundled
|
|
90
|
+
themes, docs.
|
|
91
|
+
|
|
92
|
+
### 3. URL sources: capture and link back
|
|
93
|
+
|
|
94
|
+
**Intent.** Diligence folders contain links, not just files. `backdraft ingest
|
|
95
|
+
<url>` should fetch a page, snapshot it into the registry like any other
|
|
96
|
+
source, and — the other half — artifacts should show a source's origin URL and
|
|
97
|
+
link back to it, so a claim cited to a web page carries both the frozen
|
|
98
|
+
receipt and the live pointer.
|
|
99
|
+
|
|
100
|
+
**Shape.** This is the graduation of the "HTML pages" question from Later, and
|
|
101
|
+
it needs its decision row: *the source's identity is the sha256 of the fetched
|
|
102
|
+
snapshot at ingest time*, same as a file — the URL is provenance metadata, not
|
|
103
|
+
identity. A page that changes later is a new generation; the existing drift
|
|
104
|
+
machinery already describes that. Capture: fetch (stdlib or the lightest
|
|
105
|
+
dependency that handles redirects and encodings), extract readable text into
|
|
106
|
+
the normal page/chunk structure, store the origin URL and fetch timestamp in
|
|
107
|
+
source meta. Render: sources carrying an origin URL show it in the resting
|
|
108
|
+
source list and the card, as a real link. Out of scope for this item:
|
|
109
|
+
JavaScript-rendered pages (note the limitation), authentication, and `.eml`
|
|
110
|
+
(stays in Later).
|
|
111
|
+
|
|
112
|
+
**Acceptance.** `ingest https://…` on a static page mints citable tokens;
|
|
113
|
+
`read`/`search`/`cell`-equivalents behave as for text sources; bind embeds the
|
|
114
|
+
receipt; the artifact's source entry links to the URL and the record JSON
|
|
115
|
+
carries url + fetched-at. Re-ingesting a changed page produces a new
|
|
116
|
+
generation and `drifted` behaves correctly. A network-free test suite (fixture
|
|
117
|
+
HTML served locally or loaded from disk); DESIGN.md row written; docs updated.
|
|
118
|
+
|
|
119
|
+
**Size.** Two to three days — decision row and capture first, render linkage
|
|
120
|
+
second.
|
|
121
|
+
|
|
122
|
+
## Parked
|
|
123
|
+
|
|
124
|
+
Deliberately not queued, each with the reason, so picking one up starts from
|
|
125
|
+
the objection rather than rediscovering it.
|
|
126
|
+
|
|
127
|
+
- **Exhibits view** — the evidence-first inversion. Risk: it grows the
|
|
128
|
+
artifact and pollutes a design whose strength is restraint. Needs a
|
|
129
|
+
deliberate design pass and a real audience before any code; not a
|
|
130
|
+
weekend-feature shape.
|
|
131
|
+
- **Excel region maps and range reads** — the research notes exist, but no
|
|
132
|
+
confidence the deterministic detection cascade lands well on the first
|
|
133
|
+
try. Revisit when large-sheet navigation hurts in a real corpus, with
|
|
134
|
+
test workbooks in hand.
|
|
135
|
+
- **`bd:calc` derivations** — reserved in the grammar and parsed as
|
|
136
|
+
malformed-on-purpose, which is enough for now. Full support complicates
|
|
137
|
+
binding and verification for one field-trial case.
|
|
138
|
+
- **Merged-cell and frozen-pane rendering** — ingest captures `merged` and
|
|
139
|
+
`frozen` in sheet meta and keeps them (cheap, and capture is the part you
|
|
140
|
+
cannot backfill). Frozen-pane rendering is cut; merged-cell rendering
|
|
141
|
+
waits for a real sheet that renders wrong without it.
|
|
142
|
+
|
|
143
|
+
## Later
|
|
144
|
+
|
|
145
|
+
- **Living documents** — re-ingest, re-bind, and a drift-first report: "these
|
|
146
|
+
claims cite figures that changed; cited-then vs. now." The primitives
|
|
147
|
+
(generations, `drifted`, the word-diff) all exist; the missing piece is a
|
|
148
|
+
diff-shaped report and a demo.
|
|
149
|
+
- **Substrates beyond the CLI** — SDK middleware for pipelines (the exit-code
|
|
150
|
+
and `--to json` contracts are already shaped for it), and a client-side
|
|
151
|
+
drag-drop viewer page (no upload — the file never leaves the reader's
|
|
152
|
+
machine).
|
|
153
|
+
- **Email (.eml)** — arrives in diligence folders alongside everything else;
|
|
154
|
+
identity questions (headers vs. body, attachments as child sources) deserve
|
|
155
|
+
a decision row before code. The web-page half of this item graduated to the
|
|
156
|
+
Now queue as URL sources.
|
|
157
|
+
- **Anthropic-API provider for the VLM extractor** — Cowork's sandbox
|
|
158
|
+
egress allowlist includes api.anthropic.com but not the OpenAI-compatible
|
|
159
|
+
providers, so an Anthropic-native option would let sandboxed sessions
|
|
160
|
+
ingest at full fidelity. Needs a client abstraction the vlm module
|
|
161
|
+
currently doesn't have; the documented workaround (ingest locally, the
|
|
162
|
+
registry travels with the folder) covers it meanwhile.
|
|
163
|
+
- **Distribution** — published on PyPI; the repo ships
|
|
164
|
+
`.claude-plugin/plugin.json` and `marketplace.json`, so it installs as a
|
|
165
|
+
Claude Code plugin and self-hosts its marketplace, and the AGENTS.md
|
|
166
|
+
snippet is published in the README and site docs. The community-directory
|
|
167
|
+
submission went in 2026-07-29 and is awaiting review — approval is what
|
|
168
|
+
surfaces the skills in Cowork's directory.
|
|
169
|
+
|
|
170
|
+
## Someday
|
|
171
|
+
|
|
172
|
+
- **Hosted team features** — upload, access control, shared registries.
|
|
173
|
+
Deliberately parked: the product's trust story is the self-contained file,
|
|
174
|
+
and a hosted viewer inverts it. Revisit only as a distinct teams product.
|
|
175
|
+
- **Entail at scale** — the model-judge verifier is wired (`[entail]` extra)
|
|
176
|
+
but has never been field-calibrated; needs a corpus and a rubric before it
|
|
177
|
+
is recommended anywhere.
|
|
@@ -352,8 +352,10 @@ class Registry:
|
|
|
352
352
|
# page snapshots (v0.2: evidence for the artifact)
|
|
353
353
|
def page_image(self, slug: str, number: int) -> PageImage | None: ...
|
|
354
354
|
# The stored visual snapshot of a current-extraction page: what the VLM
|
|
355
|
-
# extractor was shown (WebP q85),
|
|
356
|
-
#
|
|
355
|
+
# extractor was shown (WebP q85), the local poppler render the CLI
|
|
356
|
+
# captures after a text-layer PDF ingest, or a `snapshot-pages`
|
|
357
|
+
# backfill. None for non-PDFs, and for PDFs ingested where poppler was
|
|
358
|
+
# unavailable.
|
|
357
359
|
def save_page_image(self, extraction_id: int, number: int, *, data: bytes,
|
|
358
360
|
format: str, width: int, height: int) -> None: ...
|
|
359
361
|
def current_extraction_id(self, slug: str) -> int | None: ... # for backfill tools
|
|
@@ -370,4 +372,4 @@ class Registry:
|
|
|
370
372
|
|
|
371
373
|
## Addendum B — CLI assembly
|
|
372
374
|
|
|
373
|
-
Top-level `cli.py` (W1) owns: typer app, registry discovery (nearest `.backdraft/` walking up from cwd; `BACKDRAFT_HOME` override), session resolution (`--session` flag > `BACKDRAFT_SESSION` env > default session), and the `init` / `ingest` / `ls` / `export` commands. It mounts sub-apps `gate/cli.py` (W2: `read`, `search`, `session`), `bind/cli.py` (W3: `bind`), `render/cli.py` (W4: `render`) — each exposes `app = typer.Typer()`; the top level mounts each inside a try/except ImportError so partial merges still run. The `backdraft` console script is declared by W1.
|
|
375
|
+
Top-level `cli.py` (W1) owns: typer app, registry discovery (nearest `.backdraft/` walking up from cwd; `BACKDRAFT_HOME` override), session resolution (`--session` flag > `BACKDRAFT_SESSION` env > default session), and the `init` / `ingest` / `ls` / `export` / `snapshot-pages` commands. After each ingest, `cli.py` captures page snapshots for any PDF whose extraction carries none — the text-layer path, since `vlm` stores its own — via `extract/snapshots.py` (poppler through pdf2image, `snapshot-pages`' internals). It sits outside the extractor so `pdf-text` stays deterministic regardless of whether poppler is installed, and it is best-effort: a `SnapshotError` leaves ingest at exit 0 with a one-line note naming `snapshot-pages` as the backfill. It mounts sub-apps `gate/cli.py` (W2: `read`, `search`, `session`), `bind/cli.py` (W3: `bind`), `render/cli.py` (W4: `render`) — each exposes `app = typer.Typer()`; the top level mounts each inside a try/except ImportError so partial merges still run. The `backdraft` console script is declared by W1.
|
|
@@ -43,17 +43,15 @@ underwriting-model underwriting-model.xlsx xlsx 2 pages
|
|
|
43
43
|
note: extracted with pdf-text (the embedded text layer). Glossy or scanned PDFs extract better through a vision model: set BACKDRAFT_VLM_API_KEY in .backdraft/env.
|
|
44
44
|
```
|
|
45
45
|
|
|
46
|
-
The keyless text layer is fine for these clean generated sources.
|
|
47
|
-
artifact to show the cited pages themselves, store their snapshots — local
|
|
48
|
-
rendering, no model calls:
|
|
49
|
-
|
|
50
|
-
```bash
|
|
51
|
-
backdraft snapshot-pages t12-summary
|
|
52
|
-
```
|
|
46
|
+
The keyless text layer is fine for these clean generated sources.
|
|
53
47
|
|
|
54
48
|
Ingest picked an extractor per file (`pdf-text` for the PDF, `xlsx` for the
|
|
55
49
|
workbook), snapshotted the text, and minted every anchor: chunks for the PDF
|
|
56
|
-
pages, cells for the sheets. Nothing is citable that was not ingested.
|
|
50
|
+
pages, cells for the sheets. Nothing is citable that was not ingested. It also
|
|
51
|
+
rendered each PDF page to an image and stored it, so the artifact at the end
|
|
52
|
+
can show the cited pages themselves — local rendering through poppler, no model
|
|
53
|
+
calls. On a machine without poppler ingest prints a note instead and
|
|
54
|
+
`backdraft snapshot-pages t12-summary` backfills them whenever you like.
|
|
57
55
|
|
|
58
56
|
The note is real: this walkthrough deliberately uses the keyless text-layer
|
|
59
57
|
path so it reproduces anywhere, and our source PDF is born-digital, where that
|
|
@@ -362,12 +360,15 @@ claim occurs at the cited location, across unit, scale and format differences. I
|
|
|
362
360
|
is what makes `[a purchase price of $24.85 million](…assumptions!B10:964a)` pass
|
|
363
361
|
against a cell whose snippet is the eight characters `24850000`.
|
|
364
362
|
|
|
365
|
-
**`overlap:
|
|
363
|
+
**`overlap: pass 11, skip 4`.** Word-overlap between claim and snippet,
|
|
366
364
|
report-only by construction — it never returns `fail`, because a faithful summary
|
|
367
|
-
can share almost no vocabulary with its source. The four `
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
365
|
+
can share almost no vocabulary with its source. The four `skip`s are the overlap
|
|
366
|
+
check declining to measure wording against single-cell sources: those claims cite
|
|
367
|
+
Excel cells, and a bare number is not a sentence, so the record says it declined
|
|
368
|
+
instead of emitting a low score as though it meant something. Where it does
|
|
369
|
+
measure and finds little it says `partial` — a signal, not a fault. The quoted
|
|
370
|
+
claim in the memo is checked differently: a quotation is an exact-substring
|
|
371
|
+
assertion, and it passes.
|
|
371
372
|
|
|
372
373
|
Both checks are off by default. Out of the box, bind is pure provenance.
|
|
373
374
|
|
|
@@ -379,10 +380,6 @@ directory; the memo and the artifact are the only files you see. (A readable
|
|
|
379
380
|
markdown projection with a `## References` section is available with
|
|
380
381
|
`bind --bound` when you want one.)
|
|
381
382
|
|
|
382
|
-
The four `skip` verdicts are the overlap check declining to measure wording
|
|
383
|
-
against single-cell sources — a number is not a sentence, and the record says
|
|
384
|
-
so instead of emitting noise.
|
|
385
|
-
|
|
386
383
|
Here is what one claim looks like in the sidecar:
|
|
387
384
|
|
|
388
385
|
```json
|
|
@@ -409,8 +406,8 @@ Here is what one claim looks like in the sidecar:
|
|
|
409
406
|
},
|
|
410
407
|
{
|
|
411
408
|
"method": "overlap",
|
|
412
|
-
"status": "
|
|
413
|
-
"detail": "
|
|
409
|
+
"status": "skip",
|
|
410
|
+
"detail": "wording overlap does not apply to a single cell"
|
|
414
411
|
}
|
|
415
412
|
]
|
|
416
413
|
}
|
|
@@ -452,8 +449,10 @@ backdraft render memo.md --to html
|
|
|
452
449
|
memo.backdraft.html
|
|
453
450
|
```
|
|
454
451
|
|
|
455
|
-
[`memo.backdraft.html`](memo.backdraft.html) is one file,
|
|
456
|
-
request of any kind
|
|
452
|
+
[`memo.backdraft.html`](memo.backdraft.html) is one file, 522 KB — most of it the
|
|
453
|
+
embedded page images — with no external request of any kind: no font, no
|
|
454
|
+
stylesheet, no script that executes. `bind --lean` skips the images and takes it
|
|
455
|
+
to 126 KB, at the cost of the cited page being shown. Open it
|
|
457
456
|
from `file://` with the network off. Hover or focus a claim to reveal its
|
|
458
457
|
receipt: the verbatim snippet, the source slug and locator, the token, the full
|
|
459
458
|
snippet hash, and a badge per verdict. Below the document body an **Unresolved**
|
|
@@ -131,9 +131,11 @@ repo</a>, and agents should start from <a href="llms.txt">llms.txt</a>.</p>
|
|
|
131
131
|
<p>Python 3.13+. The vision-model extractor ships by default, the recommended
|
|
132
132
|
path for real PDFs (glossy layouts, info boxes, scans) and the only path for
|
|
133
133
|
scanned images; it activates only when <code>BACKDRAFT_VLM_API_KEY</code> is
|
|
134
|
-
set,
|
|
135
|
-
|
|
136
|
-
|
|
134
|
+
set. Without a key, PDFs fall back to the keyless embedded-text layer, and
|
|
135
|
+
ingest says so. Either way PDFs want <code>poppler</code> on your PATH
|
|
136
|
+
(<code>brew install poppler</code>) — it renders the pages, which is what puts
|
|
137
|
+
the cited page into the artifact; without it ingest still works and says what
|
|
138
|
+
is missing.
|
|
137
139
|
Spreadsheets (xlsx, xlsm), CSV, Word (docx), PowerPoint (pptx), text and
|
|
138
140
|
Markdown are keyless and built in, and sheet evidence carries the workbook's
|
|
139
141
|
own styling: bold, fills, number formats, column widths. Legacy xls
|
|
@@ -209,7 +211,7 @@ skill</a> imposes the gate, and you receive the artifact.</div>
|
|
|
209
211
|
<tr><th>Command</th><th>Does</th></tr>
|
|
210
212
|
<tr><td><code>init</code></td><td>Create <code>.backdraft/</code> (registry, credentials template) in the current directory.</td></tr>
|
|
211
213
|
<tr><td><code>ingest <files></code></td><td>Snapshot sources into the registry, minting every anchor. Formats: PDF, XLSX/XLSM, XLS, CSV/TSV, DOCX, PPTX, images (png, jpeg, tiff), text and Markdown. <code>--extractor auto|vlm|image|pdf-text|xlsx|xls|csv|docx|pptx|text</code>, <code>--slug</code>, <code>--config k=v</code>.</td></tr>
|
|
212
|
-
<tr><td><code>snapshot-pages <slug></code></td><td>Backfill page images for an already-ingested PDF, locally, no model calls (needs <code>poppler</code> for rendering).</td></tr>
|
|
214
|
+
<tr><td><code>snapshot-pages <slug></code></td><td>Backfill page images for an already-ingested PDF, locally, no model calls (needs <code>poppler</code> for rendering). Ingest stores them already — this is for a registry built before it did, or on a machine that had no poppler then.</td></tr>
|
|
213
215
|
<tr><td><code>ls</code></td><td>List ingested documents.</td></tr>
|
|
214
216
|
<tr><td><code>read [slug] [selector]</code></td><td>The gate: document list, table of contents, or a token-marked page/range/sheet read. Mints what it shows.</td></tr>
|
|
215
217
|
<tr><td><code>search <query></code></td><td>Full-text search over every anchor; hits are citable. <code>--in slug</code>, <code>--limit</code>.</td></tr>
|
|
@@ -297,6 +299,13 @@ from Cowork's built-in skills directory; until then, zip a skill folder from
|
|
|
297
299
|
the repo's <code>skills/</code> and upload it under Customize > Skills.
|
|
298
300
|
Inside a session, run the CLI per-command as <code>uvx backdraft ...</code>,
|
|
299
301
|
since installs do not persist between sessions.</p>
|
|
302
|
+
<p>Sandboxes and credentials: a sandbox usually cannot reach model providers,
|
|
303
|
+
so ingest inside one falls back to the keyless text layer and says so. For
|
|
304
|
+
full fidelity, ingest once on your own machine with the vision extractor.
|
|
305
|
+
The registry lives in <code>.backdraft/</code> inside the project folder, so
|
|
306
|
+
it travels with the folder into any session, and binding and rendering never
|
|
307
|
+
need a key. Put <code>BACKDRAFT_VLM_API_KEY</code> in
|
|
308
|
+
<code>.backdraft/env</code> yourself; never paste keys into a chat.</p>
|
|
300
309
|
<h3>Codex, Cursor, Copilot</h3>
|
|
301
310
|
<p>Agents in this family read skills from <code>~/.agents/skills</code>:</p>
|
|
302
311
|
<pre>backdraft skill install --agent codex <span class="dim"># into ~/.agents/skills/</span></pre>
|
|
@@ -339,9 +348,13 @@ registry contains the full text of everything ingested.</li>
|
|
|
339
348
|
Text-layer receipts are fine for clean digital PDFs; glossy or scanned ones
|
|
340
349
|
deserve the vision model.</p>
|
|
341
350
|
<h3>My artifact has no page images.</h3>
|
|
342
|
-
<p>
|
|
343
|
-
|
|
344
|
-
|
|
351
|
+
<p>Ingest stores them for every PDF, through both the vision and the text-layer
|
|
352
|
+
path — the latter renders the pages locally, which needs <code>poppler</code>
|
|
353
|
+
on the machine. If ingest printed a note that it could not capture them, install
|
|
354
|
+
poppler; then, for that registry or any built before ingest did this, run
|
|
355
|
+
<code>backdraft snapshot-pages <slug></code> (local, free) and re-bind.
|
|
356
|
+
Also check you did not bind with <code>--lean</code>, which skips them
|
|
357
|
+
deliberately.</p>
|
|
345
358
|
<h3>What does <code>not_shown</code> actually catch?</h3>
|
|
346
359
|
<p>A real token, valid in the registry, that the writing session was never
|
|
347
360
|
shown, i.e. the writer cited something it didn't read. Only a session ledger
|
|
@@ -22,9 +22,16 @@ uv tool install backdraft # pip install backdraft where uv is absent
|
|
|
22
22
|
# Vision-model extraction for PDFs and images ships by default (needs poppler
|
|
23
23
|
# for PDFs + BACKDRAFT_VLM_API_KEY). Ambient provider keys (OPENAI_API_KEY
|
|
24
24
|
# etc.) are NEVER read; only BACKDRAFT_*.
|
|
25
|
+
# Every PDF ingest stores each page's image (poppler renders them locally, no
|
|
26
|
+
# model calls) so artifacts show the cited page. No poppler: ingest still
|
|
27
|
+
# succeeds, notes it, and `backdraft snapshot-pages <slug>` backfills later.
|
|
25
28
|
# Formats: pdf, xlsx/xlsm, xls (via [xls]), csv/tsv, docx, pptx,
|
|
26
29
|
# png/jpeg/tiff, txt/md. pptx is slide text only; export visual-heavy
|
|
27
30
|
# decks to PDF and ingest via the vision extractor.
|
|
31
|
+
# Sandboxes usually cannot reach model providers: if VLM ingest fails on
|
|
32
|
+
# network, continue with the text layer and say so. The registry travels
|
|
33
|
+
# with the project folder (.backdraft/), so a registry ingested with the
|
|
34
|
+
# vision model elsewhere works in any session; bind and render need no key.
|
|
28
35
|
|
|
29
36
|
## The contract
|
|
30
37
|
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="utf-8">
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
6
|
+
<title>Backdraft, privacy</title>
|
|
7
|
+
<meta name="description" content="Backdraft's privacy policy: the software runs locally, collects nothing, and sends nothing anywhere unless you configure a model provider yourself.">
|
|
8
|
+
<link rel="icon" href='data:image/svg+xml,%3Csvg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64"%3E%3Crect width="64" height="64" rx="13" fill="%23FBFAF6"/%3E%3Cg transform="translate(32 32) scale(0.82) translate(-32 -32)"%3E%3Cpath fill="%23282828" fill-rule="evenodd" d="M44 3 C38 8 30 10 26 16 C23.5 20 24.5 24 27 26.5 C20.5 28 14.5 33 14 41 C13.3 51.5 22 59.5 33 59.5 C44 59.5 51.5 51.5 51 41.5 C50.6 33.5 45 29 42.5 22.5 C40.8 18 42.5 9 44 3 Z M34 34 C38 38.5 39.5 43.5 37.5 47.5 C35 52 28.5 51.5 26.5 47 C25 43.5 27.5 40 30 38.5 C31.8 37.5 33.2 36 34 34 Z"/%3E%3C/g%3E%3C/svg%3E'>
|
|
9
|
+
<style>
|
|
10
|
+
*,*::before,*::after{box-sizing:border-box}
|
|
11
|
+
:root{
|
|
12
|
+
--paper:#FFFFFF; --ink:#232323; --muted:#676767; --faint:#A3A19A;
|
|
13
|
+
--hairline:#E8E5DD; --hairline-strong:#D6D2C6;
|
|
14
|
+
--notebook:#FBFAF6; --notebook-line:rgba(40,40,40,.05);
|
|
15
|
+
--sel:#1F7244; --sel-soft:#EAF3EE; --gold:#F5E6AE; --alarm:#A63A2E;
|
|
16
|
+
--serif:'Iowan Old Style','Iowan Old Style BT',Palatino,'Palatino Linotype',Georgia,serif;
|
|
17
|
+
--sans:ui-sans-serif,system-ui,-apple-system,'Segoe UI',Helvetica,sans-serif;
|
|
18
|
+
--mono:ui-monospace,'SF Mono',Menlo,Consolas,monospace;
|
|
19
|
+
}
|
|
20
|
+
html{-webkit-text-size-adjust:100%}
|
|
21
|
+
body{margin:0;background:var(--notebook);color:var(--ink);
|
|
22
|
+
font-family:var(--serif);font-size:16.5px;line-height:1.62;
|
|
23
|
+
background-image:
|
|
24
|
+
repeating-linear-gradient(0deg,var(--notebook-line) 0 1px,transparent 1px 18px),
|
|
25
|
+
repeating-linear-gradient(90deg,var(--notebook-line) 0 1px,transparent 1px 18px)}
|
|
26
|
+
a{color:inherit;text-decoration:underline;text-decoration-color:rgba(40,40,40,.28);
|
|
27
|
+
text-underline-offset:3px;text-decoration-thickness:1px}
|
|
28
|
+
a:hover{text-decoration-color:var(--ink)}
|
|
29
|
+
.wrap{max-width:72rem;margin:0 auto;padding:0 1.5rem}
|
|
30
|
+
.bar{display:flex;align-items:center;justify-content:space-between;gap:1rem;
|
|
31
|
+
padding:1.4rem 0;font-family:var(--sans);font-size:.82rem}
|
|
32
|
+
.brand{display:flex;align-items:center;gap:.5rem;font-family:var(--serif);
|
|
33
|
+
font-weight:600;font-size:1.05rem;text-decoration:none}
|
|
34
|
+
.bar nav{display:flex;flex-wrap:wrap;justify-content:flex-end;gap:.35rem 1.3rem}
|
|
35
|
+
.bar nav a{text-decoration:none;color:var(--muted);font-weight:500;padding:.2rem 0}
|
|
36
|
+
.bar nav a:hover{color:var(--ink)}
|
|
37
|
+
main{max-width:44rem;padding:2rem 0 6rem}
|
|
38
|
+
main h1{font-size:2rem;font-weight:600;letter-spacing:-.01em;margin:.5rem 0 .6rem}
|
|
39
|
+
.lede{color:var(--muted);margin:0 0 2.5rem;font-size:1rem}
|
|
40
|
+
section{border-top:1px solid var(--hairline);padding:2.2rem 0 .8rem;margin-top:1.6rem}
|
|
41
|
+
h2{font-size:1.35rem;font-weight:600;margin:0 0 .9rem;letter-spacing:-.005em}
|
|
42
|
+
p{margin:0 0 .95rem}
|
|
43
|
+
ul{margin:0 0 .95rem;padding-left:1.3rem}
|
|
44
|
+
li{margin:.25rem 0}
|
|
45
|
+
code{font-family:var(--mono);font-size:.85em;background:rgba(40,40,40,.05);
|
|
46
|
+
border-radius:3px;padding:.05em .3em}
|
|
47
|
+
footer{text-align:center;font-family:var(--sans);font-size:.74rem;color:var(--muted);
|
|
48
|
+
padding:0 0 3rem}
|
|
49
|
+
@media (max-width:640px){
|
|
50
|
+
.wrap{padding:0 1.1rem}
|
|
51
|
+
.bar{flex-direction:column;align-items:flex-start;gap:.5rem;padding:1.1rem 0}
|
|
52
|
+
.bar nav{justify-content:flex-start;gap:.35rem 1.1rem}
|
|
53
|
+
main{padding:1.2rem 0 4rem}
|
|
54
|
+
main h1{font-size:1.7rem}
|
|
55
|
+
}
|
|
56
|
+
</style>
|
|
57
|
+
</head>
|
|
58
|
+
<body>
|
|
59
|
+
<div class="wrap">
|
|
60
|
+
|
|
61
|
+
<div class="bar">
|
|
62
|
+
<a class="brand" href="index.html">
|
|
63
|
+
<svg width="22" height="22" viewBox="0 0 64 64" aria-hidden="true"><path fill="#232323" fill-rule="evenodd" d="M44 3 C38 8 30 10 26 16 C23.5 20 24.5 24 27 26.5 C20.5 28 14.5 33 14 41 C13.3 51.5 22 59.5 33 59.5 C44 59.5 51.5 51.5 51 41.5 C50.6 33.5 45 29 42.5 22.5 C40.8 18 42.5 9 44 3 Z M34 34 C38 38.5 39.5 43.5 37.5 47.5 C35 52 28.5 51.5 26.5 47 C25 43.5 27.5 40 30 38.5 C31.8 37.5 33.2 36 34 34 Z"/></svg>
|
|
64
|
+
backdraft
|
|
65
|
+
</a>
|
|
66
|
+
<nav>
|
|
67
|
+
<a href="docs.html">Docs</a>
|
|
68
|
+
<a href="demo.html">Demo</a>
|
|
69
|
+
<a href="https://github.com/spencerbraun/backdraft">GitHub</a>
|
|
70
|
+
</nav>
|
|
71
|
+
</div>
|
|
72
|
+
|
|
73
|
+
<main>
|
|
74
|
+
<h1>Privacy</h1>
|
|
75
|
+
<p class="lede">Backdraft is local software. It collects nothing.</p>
|
|
76
|
+
|
|
77
|
+
<section>
|
|
78
|
+
<h2>What Backdraft collects</h2>
|
|
79
|
+
<p>Nothing. The CLI and the agent skills run entirely on your machine. There
|
|
80
|
+
is no telemetry, no analytics, no usage reporting, no crash reporting, and no
|
|
81
|
+
account. We do not see your documents, your queries, or the fact that you use
|
|
82
|
+
Backdraft at all.</p>
|
|
83
|
+
<p>Everything Backdraft produces stays where you put it: the registry and its
|
|
84
|
+
snapshots live in a <code>.backdraft/</code> directory inside your project
|
|
85
|
+
folder, and the rendered artifact is a single self-contained HTML file that
|
|
86
|
+
works with no network connection.</p>
|
|
87
|
+
</section>
|
|
88
|
+
|
|
89
|
+
<section>
|
|
90
|
+
<h2>When data leaves your machine</h2>
|
|
91
|
+
<p>Only when you configure it, and only to a provider you chose. If you set
|
|
92
|
+
<code>BACKDRAFT_VLM_API_KEY</code>, page images or document images are sent
|
|
93
|
+
to that vision-model provider during ingest; if you set
|
|
94
|
+
<code>BACKDRAFT_ENTAIL_API_KEY</code>, claim and evidence text is sent to
|
|
95
|
+
that provider during the optional entailment check. Without those keys,
|
|
96
|
+
nothing is ever transmitted anywhere.</p>
|
|
97
|
+
<p>Backdraft deliberately never reads ambient provider keys such as
|
|
98
|
+
<code>OPENAI_API_KEY</code>: a key in your environment is not consent to send
|
|
99
|
+
your documents to its provider. Consent is a <code>BACKDRAFT_</code>-scoped
|
|
100
|
+
setting you write yourself.</p>
|
|
101
|
+
<p>Data sent to a provider you configure is governed by that provider's
|
|
102
|
+
privacy policy, not this one.</p>
|
|
103
|
+
</section>
|
|
104
|
+
|
|
105
|
+
<section>
|
|
106
|
+
<h2>This website</h2>
|
|
107
|
+
<p>backdraft.dev is a static site. It sets no cookies and runs no analytics.
|
|
108
|
+
Standard web server logs may record requests, as with any website.</p>
|
|
109
|
+
</section>
|
|
110
|
+
|
|
111
|
+
<section>
|
|
112
|
+
<h2>Changes and contact</h2>
|
|
113
|
+
<p>If this policy changes, the change lands in the
|
|
114
|
+
<a href="https://github.com/spencerbraun/backdraft">public repository</a>
|
|
115
|
+
with its full history. Questions: open an issue there.</p>
|
|
116
|
+
<p>Last updated: July 29, 2026.</p>
|
|
117
|
+
</section>
|
|
118
|
+
|
|
119
|
+
</main>
|
|
120
|
+
</div>
|
|
121
|
+
|
|
122
|
+
<footer>
|
|
123
|
+
Backdraft · MIT · <a href="index.html">Home</a> · <a href="https://github.com/spencerbraun/backdraft">GitHub</a>
|
|
124
|
+
</footer>
|
|
125
|
+
</body>
|
|
126
|
+
</html>
|
|
@@ -29,11 +29,18 @@ export BACKDRAFT_SESSION=s-<short-name>
|
|
|
29
29
|
For real PDFs (glossy layouts, info boxes, scans) the VLM extractor produces the
|
|
30
30
|
best receipts; `auto` prefers it when `BACKDRAFT_VLM_API_KEY` is set (env or `.backdraft/env`; ambient provider keys
|
|
31
31
|
are never read) — the default model is Gemini 3.1 Flash Lite through OpenRouter. If ingest prints a note about falling back to the text
|
|
32
|
-
layer, surface that note to the user — it affects receipt quality.
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
32
|
+
layer, surface that note to the user — it affects receipt quality. In a
|
|
33
|
+
sandbox that cannot reach the model provider, do not retry: continue with the
|
|
34
|
+
text layer, tell the user, and mention that a registry ingested with the
|
|
35
|
+
vision model on their own machine travels with the project folder
|
|
36
|
+
(`.backdraft/`) and needs no key at bind or render time.
|
|
37
|
+
|
|
38
|
+
Ingest also stores each PDF page's image — both paths, since the text-layer
|
|
39
|
+
path renders the pages locally through poppler — so the artifact can show the
|
|
40
|
+
cited pages themselves. If ingest prints a note that page images were not
|
|
41
|
+
captured, the machine has no poppler; ingest and citations are unaffected, and
|
|
42
|
+
`backdraft snapshot-pages <slug>` backfills the images later (also the fix for
|
|
43
|
+
a registry built before ingest did this). No model calls either way.
|
|
37
44
|
|
|
38
45
|
Then read, narrowing as you go:
|
|
39
46
|
|