backdraft 0.2.0__tar.gz → 0.3.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.3.0/.github/workflows/ci.yml +14 -0
- backdraft-0.3.0/.github/workflows/release.yml +23 -0
- backdraft-0.3.0/.github/workflows/site.yml +26 -0
- {backdraft-0.2.0 → backdraft-0.3.0}/.gitignore +1 -0
- {backdraft-0.2.0 → backdraft-0.3.0}/DESIGN.md +19 -19
- {backdraft-0.2.0 → backdraft-0.3.0}/PKG-INFO +34 -31
- {backdraft-0.2.0 → backdraft-0.3.0}/README.md +29 -30
- {backdraft-0.2.0 → backdraft-0.3.0}/ROADMAP.md +20 -21
- {backdraft-0.2.0 → backdraft-0.3.0}/SPEC.md +4 -4
- {backdraft-0.2.0 → backdraft-0.3.0}/demo/generate_sources.py +31 -2
- {backdraft-0.2.0 → backdraft-0.3.0}/demo/memo.backdraft.html +396 -17
- {backdraft-0.2.0 → backdraft-0.3.0}/demo/sources/t12-summary.pdf +2 -2
- backdraft-0.3.0/demo/sources/underwriting-model.xlsx +0 -0
- {backdraft-0.2.0 → backdraft-0.3.0}/pyproject.toml +10 -1
- {backdraft-0.2.0 → backdraft-0.3.0}/site/demo.html +396 -17
- {backdraft-0.2.0 → backdraft-0.3.0}/site/docs.html +62 -37
- backdraft-0.3.0/site/index.html +459 -0
- {backdraft-0.2.0 → backdraft-0.3.0}/site/llms.txt +10 -7
- {backdraft-0.2.0 → backdraft-0.3.0}/spec/artifact.md +12 -4
- {backdraft-0.2.0 → backdraft-0.3.0}/src/backdraft/bind/evidence.py +44 -7
- {backdraft-0.2.0 → backdraft-0.3.0}/src/backdraft/cli.py +53 -0
- {backdraft-0.2.0 → backdraft-0.3.0}/src/backdraft/extract/base.py +20 -4
- backdraft-0.3.0/src/backdraft/extract/csv.py +97 -0
- backdraft-0.3.0/src/backdraft/extract/image.py +88 -0
- {backdraft-0.2.0 → backdraft-0.3.0}/src/backdraft/extract/vlm.py +3 -4
- {backdraft-0.2.0 → backdraft-0.3.0}/src/backdraft/extract/vlm_settings.py +11 -11
- {backdraft-0.2.0 → backdraft-0.3.0}/src/backdraft/extract/xlsx.py +154 -10
- {backdraft-0.2.0 → backdraft-0.3.0}/src/backdraft/gate/searcher.py +3 -3
- {backdraft-0.2.0 → backdraft-0.3.0}/src/backdraft/kernel/artifact.py +7 -2
- {backdraft-0.2.0 → backdraft-0.3.0}/src/backdraft/kernel/model.py +8 -2
- {backdraft-0.2.0 → backdraft-0.3.0}/src/backdraft/registry/ledger.py +1 -1
- {backdraft-0.2.0 → backdraft-0.3.0}/src/backdraft/registry/schema.sql +8 -1
- {backdraft-0.2.0 → backdraft-0.3.0}/src/backdraft/registry/store.py +37 -5
- {backdraft-0.2.0 → backdraft-0.3.0}/src/backdraft/render/html.py +134 -16
- {backdraft-0.2.0 → backdraft-0.3.0}/tests/golden/render/backfill.sidecar.json +1 -1
- {backdraft-0.2.0 → backdraft-0.3.0}/tests/golden/render/demo.sidecar.json +1 -1
- {backdraft-0.2.0 → backdraft-0.3.0}/tests/test_bind_evidence.py +35 -0
- {backdraft-0.2.0 → backdraft-0.3.0}/tests/test_cli.py +17 -0
- {backdraft-0.2.0 → backdraft-0.3.0}/tests/test_extract.py +163 -4
- {backdraft-0.2.0 → backdraft-0.3.0}/uv.lock +1 -1
- backdraft-0.2.0/demo/sources/underwriting-model.xlsx +0 -0
- backdraft-0.2.0/research/20260727-excel-region-detection.md +0 -53
- backdraft-0.2.0/research/20260727-research-findings.md +0 -182
- backdraft-0.2.0/site/index.html +0 -410
- {backdraft-0.2.0 → backdraft-0.3.0}/.python-version +0 -0
- {backdraft-0.2.0 → backdraft-0.3.0}/LICENSE +0 -0
- {backdraft-0.2.0 → backdraft-0.3.0}/assets/backdraft-mark.svg +0 -0
- {backdraft-0.2.0 → backdraft-0.3.0}/demo/memo.md +0 -0
- {backdraft-0.2.0 → backdraft-0.3.0}/demo/walkthrough.md +0 -0
- {backdraft-0.2.0 → backdraft-0.3.0}/skills/backdraft/SKILL.md +0 -0
- {backdraft-0.2.0 → backdraft-0.3.0}/skills/backdraft-artifact/SKILL.md +0 -0
- {backdraft-0.2.0 → backdraft-0.3.0}/skills/backdraft-backfill/SKILL.md +0 -0
- {backdraft-0.2.0 → backdraft-0.3.0}/spec/chunking.md +0 -0
- {backdraft-0.2.0 → backdraft-0.3.0}/spec/tokens.md +0 -0
- {backdraft-0.2.0 → backdraft-0.3.0}/src/backdraft/__init__.py +0 -0
- {backdraft-0.2.0 → backdraft-0.3.0}/src/backdraft/bind/__init__.py +0 -0
- {backdraft-0.2.0 → backdraft-0.3.0}/src/backdraft/bind/binder.py +0 -0
- {backdraft-0.2.0 → backdraft-0.3.0}/src/backdraft/bind/cli.py +0 -0
- {backdraft-0.2.0 → backdraft-0.3.0}/src/backdraft/bind/verify/__init__.py +0 -0
- {backdraft-0.2.0 → backdraft-0.3.0}/src/backdraft/bind/verify/base.py +0 -0
- {backdraft-0.2.0 → backdraft-0.3.0}/src/backdraft/bind/verify/entail.py +0 -0
- {backdraft-0.2.0 → backdraft-0.3.0}/src/backdraft/bind/verify/overlap.py +0 -0
- {backdraft-0.2.0 → backdraft-0.3.0}/src/backdraft/bind/verify/recompute.py +0 -0
- {backdraft-0.2.0 → backdraft-0.3.0}/src/backdraft/bind/verify/value_trace.py +0 -0
- {backdraft-0.2.0 → backdraft-0.3.0}/src/backdraft/cli_context.py +0 -0
- {backdraft-0.2.0 → backdraft-0.3.0}/src/backdraft/credentials.py +0 -0
- {backdraft-0.2.0 → backdraft-0.3.0}/src/backdraft/extract/__init__.py +0 -0
- {backdraft-0.2.0 → backdraft-0.3.0}/src/backdraft/extract/pdf_text.py +0 -0
- {backdraft-0.2.0 → backdraft-0.3.0}/src/backdraft/extract/text.py +0 -0
- {backdraft-0.2.0 → backdraft-0.3.0}/src/backdraft/gate/__init__.py +0 -0
- {backdraft-0.2.0 → backdraft-0.3.0}/src/backdraft/gate/cli.py +0 -0
- {backdraft-0.2.0 → backdraft-0.3.0}/src/backdraft/gate/reader.py +0 -0
- {backdraft-0.2.0 → backdraft-0.3.0}/src/backdraft/kernel/__init__.py +0 -0
- {backdraft-0.2.0 → backdraft-0.3.0}/src/backdraft/kernel/chunking.py +0 -0
- {backdraft-0.2.0 → backdraft-0.3.0}/src/backdraft/kernel/claims.py +0 -0
- {backdraft-0.2.0 → backdraft-0.3.0}/src/backdraft/kernel/errors.py +0 -0
- {backdraft-0.2.0 → backdraft-0.3.0}/src/backdraft/kernel/hashing.py +0 -0
- {backdraft-0.2.0 → backdraft-0.3.0}/src/backdraft/kernel/tokens.py +0 -0
- {backdraft-0.2.0 → backdraft-0.3.0}/src/backdraft/registry/__init__.py +0 -0
- {backdraft-0.2.0 → backdraft-0.3.0}/src/backdraft/render/__init__.py +0 -0
- {backdraft-0.2.0 → backdraft-0.3.0}/src/backdraft/render/_text.py +0 -0
- {backdraft-0.2.0 → backdraft-0.3.0}/src/backdraft/render/cli.py +0 -0
- {backdraft-0.2.0 → backdraft-0.3.0}/src/backdraft/render/footnotes.py +0 -0
- {backdraft-0.2.0 → backdraft-0.3.0}/src/backdraft/render/markdown.py +0 -0
- {backdraft-0.2.0 → backdraft-0.3.0}/src/backdraft/render/placement.py +0 -0
- {backdraft-0.2.0 → backdraft-0.3.0}/src/backdraft/render/sidecar.py +0 -0
- {backdraft-0.2.0 → backdraft-0.3.0}/tests/conftest.py +0 -0
- {backdraft-0.2.0 → backdraft-0.3.0}/tests/conftest_registry.py +0 -0
- {backdraft-0.2.0 → backdraft-0.3.0}/tests/conftest_render.py +0 -0
- {backdraft-0.2.0 → backdraft-0.3.0}/tests/fakes.py +0 -0
- {backdraft-0.2.0 → backdraft-0.3.0}/tests/gate/conftest.py +0 -0
- {backdraft-0.2.0 → backdraft-0.3.0}/tests/gate/fake_registry.py +0 -0
- {backdraft-0.2.0 → backdraft-0.3.0}/tests/gate/test_gate_cli.py +0 -0
- {backdraft-0.2.0 → backdraft-0.3.0}/tests/gate/test_gate_ledger.py +0 -0
- {backdraft-0.2.0 → backdraft-0.3.0}/tests/gate/test_gate_reader.py +0 -0
- {backdraft-0.2.0 → backdraft-0.3.0}/tests/gate/test_gate_searcher.py +0 -0
- {backdraft-0.2.0 → backdraft-0.3.0}/tests/gate/test_reader_cells.py +0 -0
- {backdraft-0.2.0 → backdraft-0.3.0}/tests/golden/bind/backfill.bound.md +0 -0
- {backdraft-0.2.0 → backdraft-0.3.0}/tests/golden/bind/frontwalk.bound.md +0 -0
- {backdraft-0.2.0 → backdraft-0.3.0}/tests/golden/chunking/long_tail.json +0 -0
- {backdraft-0.2.0 → backdraft-0.3.0}/tests/golden/chunking/long_tail.txt +0 -0
- {backdraft-0.2.0 → backdraft-0.3.0}/tests/golden/chunking/many_small.json +0 -0
- {backdraft-0.2.0 → backdraft-0.3.0}/tests/golden/chunking/many_small.txt +0 -0
- {backdraft-0.2.0 → backdraft-0.3.0}/tests/golden/chunking/mixed.json +0 -0
- {backdraft-0.2.0 → backdraft-0.3.0}/tests/golden/chunking/mixed.txt +0 -0
- {backdraft-0.2.0 → backdraft-0.3.0}/tests/golden/chunking/one_giant.json +0 -0
- {backdraft-0.2.0 → backdraft-0.3.0}/tests/golden/chunking/one_giant.txt +0 -0
- {backdraft-0.2.0 → backdraft-0.3.0}/tests/golden/chunking/short_page.json +0 -0
- {backdraft-0.2.0 → backdraft-0.3.0}/tests/golden/chunking/short_page.txt +0 -0
- {backdraft-0.2.0 → backdraft-0.3.0}/tests/golden/chunking/unsplittable.json +0 -0
- {backdraft-0.2.0 → backdraft-0.3.0}/tests/golden/chunking/unsplittable.txt +0 -0
- {backdraft-0.2.0 → backdraft-0.3.0}/tests/golden/render/backfill.footnotes.md +0 -0
- {backdraft-0.2.0 → backdraft-0.3.0}/tests/golden/render/demo.footnotes.md +0 -0
- {backdraft-0.2.0 → backdraft-0.3.0}/tests/golden_util.py +0 -0
- {backdraft-0.2.0 → backdraft-0.3.0}/tests/test_bind_binder.py +0 -0
- {backdraft-0.2.0 → backdraft-0.3.0}/tests/test_bind_cli.py +0 -0
- {backdraft-0.2.0 → backdraft-0.3.0}/tests/test_bind_verify.py +0 -0
- {backdraft-0.2.0 → backdraft-0.3.0}/tests/test_chunking.py +0 -0
- {backdraft-0.2.0 → backdraft-0.3.0}/tests/test_claims.py +0 -0
- {backdraft-0.2.0 → backdraft-0.3.0}/tests/test_credentials.py +0 -0
- {backdraft-0.2.0 → backdraft-0.3.0}/tests/test_footnotes.py +0 -0
- {backdraft-0.2.0 → backdraft-0.3.0}/tests/test_gate_integration.py +0 -0
- {backdraft-0.2.0 → backdraft-0.3.0}/tests/test_hashing.py +0 -0
- {backdraft-0.2.0 → backdraft-0.3.0}/tests/test_html.py +0 -0
- {backdraft-0.2.0 → backdraft-0.3.0}/tests/test_invariants.py +0 -0
- {backdraft-0.2.0 → backdraft-0.3.0}/tests/test_model.py +0 -0
- {backdraft-0.2.0 → backdraft-0.3.0}/tests/test_placement.py +0 -0
- {backdraft-0.2.0 → backdraft-0.3.0}/tests/test_records_location.py +0 -0
- {backdraft-0.2.0 → backdraft-0.3.0}/tests/test_registry.py +0 -0
- {backdraft-0.2.0 → backdraft-0.3.0}/tests/test_reingest.py +0 -0
- {backdraft-0.2.0 → backdraft-0.3.0}/tests/test_render_cli.py +0 -0
- {backdraft-0.2.0 → backdraft-0.3.0}/tests/test_render_markdown.py +0 -0
- {backdraft-0.2.0 → backdraft-0.3.0}/tests/test_sidecar.py +0 -0
- {backdraft-0.2.0 → backdraft-0.3.0}/tests/test_tokens.py +0 -0
- {backdraft-0.2.0 → backdraft-0.3.0}/tests/test_value_trace.py +0 -0
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
name: release
|
|
2
|
+
on:
|
|
3
|
+
release:
|
|
4
|
+
types: [published]
|
|
5
|
+
jobs:
|
|
6
|
+
test:
|
|
7
|
+
runs-on: ubuntu-latest
|
|
8
|
+
steps:
|
|
9
|
+
- uses: actions/checkout@v4
|
|
10
|
+
- uses: astral-sh/setup-uv@v5
|
|
11
|
+
- run: uv sync --all-extras
|
|
12
|
+
- run: uv run pytest
|
|
13
|
+
publish:
|
|
14
|
+
needs: test
|
|
15
|
+
runs-on: ubuntu-latest
|
|
16
|
+
environment: pypi
|
|
17
|
+
permissions:
|
|
18
|
+
id-token: write # trusted publishing: OIDC, no stored token
|
|
19
|
+
steps:
|
|
20
|
+
- uses: actions/checkout@v4
|
|
21
|
+
- uses: astral-sh/setup-uv@v5
|
|
22
|
+
- run: uv build
|
|
23
|
+
- uses: pypa/gh-action-pypi-publish@release/v1
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
name: site
|
|
2
|
+
on:
|
|
3
|
+
push:
|
|
4
|
+
branches: [main]
|
|
5
|
+
paths: ["site/**"]
|
|
6
|
+
workflow_dispatch:
|
|
7
|
+
permissions:
|
|
8
|
+
contents: read
|
|
9
|
+
pages: write
|
|
10
|
+
id-token: write
|
|
11
|
+
concurrency:
|
|
12
|
+
group: pages
|
|
13
|
+
cancel-in-progress: true
|
|
14
|
+
jobs:
|
|
15
|
+
deploy:
|
|
16
|
+
runs-on: ubuntu-latest
|
|
17
|
+
environment:
|
|
18
|
+
name: github-pages
|
|
19
|
+
url: ${{ steps.deployment.outputs.page_url }}
|
|
20
|
+
steps:
|
|
21
|
+
- uses: actions/checkout@v4
|
|
22
|
+
- uses: actions/upload-pages-artifact@v3
|
|
23
|
+
with:
|
|
24
|
+
path: site/
|
|
25
|
+
- id: deployment
|
|
26
|
+
uses: actions/deploy-pages@v4
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
# Backdraft — Design
|
|
2
2
|
|
|
3
|
-
Status: v0 design, 2026-07-27. Decisions below are made unless marked open.
|
|
3
|
+
Status: v0 design, 2026-07-27. Decisions below are made unless marked open.
|
|
4
4
|
|
|
5
5
|
## The point
|
|
6
6
|
|
|
7
|
-
Drop-in provenance for factual claims: one click from any claim to the evidence behind it.
|
|
7
|
+
Drop-in provenance for factual claims: one click from any claim to the evidence behind it. The working problem statement is **verification cost** — an analyst who can't show where a number came from either re-reads the document or doesn't use the output, and both outcomes destroy the value of extraction.
|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
Designed from first principles against the failure modes that kill citation systems in practice: random anchor ids, competing citation grammars, duplicated resolvers, silent failure, anchors that die on reprocessing.
|
|
10
10
|
|
|
11
11
|
## Principles
|
|
12
12
|
|
|
@@ -14,7 +14,7 @@ Not a port. The app proved the product (citations worked great from the frontend
|
|
|
14
14
|
2. **Content-addressed everything.** Anchors derive their identity from document content + location, not from a run. Reprocessing the same bytes yields the same anchors; citations survive.
|
|
15
15
|
3. **The receipt travels with the claim.** An anchor is not a pointer — it is a pointer plus captured evidence: verbatim snippet, content hash, location. Defensible without reopening the source.
|
|
16
16
|
4. **Failures are data.** Unresolvable tokens, drifted sources, unmatched claims are first-class records in reports and artifacts — never warn-and-drop.
|
|
17
|
-
5. **Coordinates live in-band.**
|
|
17
|
+
5. **Coordinates live in-band.** Sub-page attribution works when markers like `[B10]` sit in the text the model reads. Human-readable location in the token; side-channel geometry rots (a bbox plumbed through six layers ends up read by nothing).
|
|
18
18
|
6. **The format is the product.** Substrates (skill, CLI, SDK, viewer) are thin shells over one spec + one library. Artifacts are self-describing and outlive any substrate (subtext's `$format` + embedded legend pattern).
|
|
19
19
|
|
|
20
20
|
## Architecture: three layers
|
|
@@ -27,13 +27,13 @@ Not a port. The app proved the product (citations worked great from the frontend
|
|
|
27
27
|
### 2. The library / CLI (the system)
|
|
28
28
|
Four verbs:
|
|
29
29
|
- **`ingest`** — deterministically anchor source documents (PDF/xlsx/md/…) into the registry: extract, locate, snapshot snippets, hash.
|
|
30
|
-
- **`read` / `search`** — the gate. Emit token-marked context for a model. Search results are themselves mintable evidence (
|
|
30
|
+
- **`read` / `search`** — the gate. Emit token-marked context for a model. Search results are themselves mintable evidence (search that returns nothing citable forces a page read purely to obtain an anchor).
|
|
31
31
|
- **`bind`** — postprocess a written document: resolve tokens, run enabled verification switches, backfill surface forms (doc names, pages, quotes), emit sidecar claims file + binding report.
|
|
32
32
|
- **`render`** — produce the clickable artifact.
|
|
33
33
|
|
|
34
34
|
### 3. Substrates (sequenced)
|
|
35
35
|
1. **Skill + CLI, together** (first release). The CLI is the system; the skill is ~a page instructing an agent to use it. Ships both the agent-native wedge and the zero-integration backfill demo.
|
|
36
|
-
2. **SDK middleware** — wrap a model call: minted context in, bound citations out. The
|
|
36
|
+
2. **SDK middleware** — wrap a model call: minted context in, bound citations out. The drop-in for application developers.
|
|
37
37
|
3. **Viewer** — the artifact carries its own single-file viewer; a component version for embedding comes later.
|
|
38
38
|
|
|
39
39
|
## The registry
|
|
@@ -43,7 +43,7 @@ Four verbs:
|
|
|
43
43
|
Tables (shape, not final DDL):
|
|
44
44
|
- `documents` — path, display name, **slug**, content sha256, media type, ingest metadata.
|
|
45
45
|
- `anchors` — token, document, locator, verbatim snippet, snippet sha256, created-by (ingest run).
|
|
46
|
-
- `ledger` — session id, token, when shown. Records every token minted into context. Bind distinguishes "cited what you saw" from "cited a valid token you were never shown" — a caught hallucination class
|
|
46
|
+
- `ledger` — session id, token, when shown. Records every token minted into context. Bind distinguishes "cited what you saw" from "cited a valid token you were never shown" — a caught hallucination class most systems cannot express.
|
|
47
47
|
- `claims` (written at bind) — claim span text, source doc, citation tokens, verification records.
|
|
48
48
|
|
|
49
49
|
FTS5 backs `search`. No server, no tenancy — single-analyst/single-project scope; multi-user is a substrate concern, not a registry concern.
|
|
@@ -54,7 +54,7 @@ FTS5 backs `search`. No server, no tenancy — single-analyst/single-project sco
|
|
|
54
54
|
|
|
55
55
|
- `slug` — short kebab doc identifier assigned at ingest, unique per registry (`t12-audit`, `rent-roll-2025`).
|
|
56
56
|
- `locator` — media-native:
|
|
57
|
-
- PDF/text: `p8` (page), `p8.c3` (chunk ordinal within page; chunking is deterministic with an explicit within-page ordinal
|
|
57
|
+
- PDF/text: `p8` (page), `p8.c3` (chunk ordinal within page; chunking is deterministic with an explicit within-page ordinal)
|
|
58
58
|
- Excel: `Rent!B10`, `Rent!B10:C12` (sheet + cell/range, spreadsheet-native)
|
|
59
59
|
- Prose files: heading-path or line-range forms (open detail)
|
|
60
60
|
- `hash` — first 4+ hex of sha256 of the normalized snippet. Catches transcription typos, detects drift after re-ingest, and makes the token content-addressed. Registry extends length on collision.
|
|
@@ -65,11 +65,11 @@ Reserved extension — **declared derivations**: a claim whose value appears in
|
|
|
65
65
|
|
|
66
66
|
## The gate (context construction)
|
|
67
67
|
|
|
68
|
-
|
|
68
|
+
The read tool's shape:
|
|
69
69
|
- Unified dispatch: list docs → per-doc TOC of page/sheet summaries → page/range read → batch read. Short slugs everywhere.
|
|
70
70
|
- Progressive disclosure with continuation, plus search hints.
|
|
71
|
-
- **Excel**: sheet-per-page markdown table with `Row | A | B | …` headers and `[B10] value` cell prefixes — the representation that
|
|
72
|
-
-
|
|
71
|
+
- **Excel**: sheet-per-page markdown table with `Row | A | B | …` headers and `[B10] value` cell prefixes — the representation that makes cell-level attribution work. Hardening: dimension caps, inflated-sheet placeholders, trimming.
|
|
72
|
+
- Search mints citable snippet tokens; Excel long-context gets structural navigation (detected regions/ranges) rather than char-offset slicing (open design area).
|
|
73
73
|
|
|
74
74
|
The skill's core instruction is a substitution: for source documents, use `backdraft read`/`search`, not raw Read/Grep.
|
|
75
75
|
|
|
@@ -97,11 +97,11 @@ Independent checks per citation, recorded as graded evidence, never gating gener
|
|
|
97
97
|
| 3 | `entail` | Model judge: does snippet support claim — tiny closed question, batchable, async | qualitative |
|
|
98
98
|
| 4 | `recompute` | Deterministic re-execution of declared derivations | derived values |
|
|
99
99
|
|
|
100
|
-
Rationale for default-off: out of the box this is pure provenance (one click to the receipt); verification is opt-in per claim class. Rung-pass rates aggregated per run/model double as an eval substrate
|
|
100
|
+
Rationale for default-off: out of the box this is pure provenance (one click to the receipt); verification is opt-in per claim class. Rung-pass rates aggregated per run/model double as an eval substrate.
|
|
101
101
|
|
|
102
102
|
## Modes
|
|
103
103
|
|
|
104
|
-
- **Front-walk** — citations minted during generation; bind resolves against the session ledger. The high-quality integrated path
|
|
104
|
+
- **Front-walk** — citations minted during generation; bind resolves against the session ledger. The high-quality integrated path.
|
|
105
105
|
- **Backfill** — an existing document + ingested sources; for each claim: search → propose anchors → bind via the fuzzier strategies (overlap, value-trace, structured lookup). Unmatched claims land in an explicit unresolved list, not silent non-attribution. Zero integration required — the demo wedge.
|
|
106
106
|
|
|
107
107
|
Same anchors, same bind, same artifact out.
|
|
@@ -127,9 +127,9 @@ Self-contained HTML (plus the machine-readable sidecar): **quotes + receipts + e
|
|
|
127
127
|
| 2026-07-27 | CLI unified on the gate's patterns: shared `cli_context` (constants, discovery, sessions, one error guard mapping `BackdraftError` → exit 1), `Annotated` params, relative imports. Test fakes renamed so the real and fake registries cannot be confused. |
|
|
128
128
|
| 2026-07-27 | File naming is part of the artifact format, owned by `kernel/artifact.py`: `<stem>.bound.md`, `<stem>.backdraft.json`, `<stem>.backdraft.html`, `<stem>.footnotes.md`. |
|
|
129
129
|
| 2026-07-27 | `bind --check entail` without the extra records `skip` verdicts carrying the reason — verifiers never gate, including at the CLI layer. |
|
|
130
|
-
| 2026-07-27 | **Supersedes "VLM when key present":** credentials are explicit. Ambient provider keys (`OPENAI_API_KEY`, `OPENROUTER_API_KEY`, `ANTHROPIC_API_KEY`) are never read; a credential reaches backdraft only via `--config`, a `BACKDRAFT_*` variable, or `.backdraft/env` (`init` writes a template). `vlm_ready` — the `auto` gate — is therefore equivalent to consent. Decided after a real unapproved-spend incident during the
|
|
130
|
+
| 2026-07-27 | **Supersedes "VLM when key present":** credentials are explicit. Ambient provider keys (`OPENAI_API_KEY`, `OPENROUTER_API_KEY`, `ANTHROPIC_API_KEY`) are never read; a credential reaches backdraft only via `--config`, a `BACKDRAFT_*` variable, or `.backdraft/env` (`init` writes a template). `vlm_ready` — the `auto` gate — is therefore equivalent to consent. Decided after a real unapproved-spend incident during the first field trial: presence of a generic key in the environment is not consent to spend it or send documents to its provider. |
|
|
131
131
|
| 2026-07-28 | **Reader doctrine (supersedes "no script"):** the artifact is designed for two readers — the writer verifying the AI's work, and a normal person the document is sent to — in three disclosure layers: the document (editorial typography, quiet marks), the footnote (click a claim → the source's own words, named and paged — no tokens, no hashes, no jargon), and the record (one deliberate step to the full machine-readable layer). **Success is silent; only failure speaks**: a fully-resolved artifact looks like a well-made document, while unresolved citations announce themselves before the reader invests belief. The load-bearing constraint is *no network, single file* — enforced by a CSP `meta` tag (`default-src 'none'`) — not *no script*: inline JS is allowed as progressive enhancement over a CSS-only baseline that survives script-stripping. Decided after field use: the v1 artifact rendered success and failure at equal volume and read as machinery, not a document. |
|
|
132
|
-
| 2026-07-28 | **Evidence embeds in the artifact (supersedes "no page images in v0"):** the footnote layer carries the evidence itself, not just the quote — the cited page as an image, the cited cell in a mini-grid window (neighbors, row labels, column headers). Bounded by *what is cited*, never the corpus, so a memo citing ten pages costs ~2–3 MB; `--lean` skips images. Evidence is assembled at **bind** (the step that has the registry) and travels in the sidecar; render stays registry-blind and the artifact stays reproducible from the two files a reader was handed. For the VLM path, the page image the model was shown is stored at **ingest** — for a non-deterministic extractor the input pixels are the only reproducible snapshot, so the image completes the receipt chain (claim → quote → page). Reference shapes
|
|
132
|
+
| 2026-07-28 | **Evidence embeds in the artifact (supersedes "no page images in v0"):** the footnote layer carries the evidence itself, not just the quote — the cited page as an image, the cited cell in a mini-grid window (neighbors, row labels, column headers). Bounded by *what is cited*, never the corpus, so a memo citing ten pages costs ~2–3 MB; `--lean` skips images. Evidence is assembled at **bind** (the step that has the registry) and travels in the sidecar; render stays registry-blind and the artifact stays reproducible from the two files a reader was handed. For the VLM path, the page image the model was shown is stored at **ingest** — for a non-deterministic extractor the input pixels are the only reproducible snapshot, so the image completes the receipt chain (claim → quote → page). Reference shapes: WebP quality 85 for pages; sheet windows as compact JSON with typed cell values and column-letter headers. |
|
|
133
133
|
| 2026-07-28 | **Exhibits: the evidence-first inversion.** The artifact carries a second reading mode: every source document with the data points cited from it, each with its evidence context and backlinks to the claims that lean on it — "this memo rests on N data points across M documents." Ranking is deterministic in v1 (an anchor cited by three claims outranks one cited once); model-judged importance is a later switch. This is the skeptical reader's entry point and the writer's spot-check checklist. |
|
|
134
134
|
|
|
135
135
|
## Open
|
|
@@ -139,8 +139,8 @@ substrates); this list is questions still awaiting a decision:
|
|
|
139
139
|
|
|
140
140
|
- Re-bind/orphan pass on re-ingest of changed docs (chunk ordinal drift)
|
|
141
141
|
- Prose-file locator forms; slug assignment/collision rules (the trailing-hyphen
|
|
142
|
-
truncation from the
|
|
143
|
-
- Distribution: PyPI
|
|
144
|
-
|
|
145
|
-
- First external user (
|
|
142
|
+
truncation from the first field trial lives here)
|
|
143
|
+
- Distribution: published to PyPI as `backdraft` 0.2.0 (2026-07-28); domain
|
|
144
|
+
backdraft.dev acquired; still open: skill registration, deploying site/
|
|
145
|
+
- First external user (design-partner validation layer vs eval substrate vs
|
|
146
146
|
open release feedback)
|
|
@@ -1,7 +1,11 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: backdraft
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.3.0
|
|
4
4
|
Summary: Drop-in provenance for factual claims: one click from any claim to the evidence behind it.
|
|
5
|
+
Project-URL: Homepage, https://backdraft.dev
|
|
6
|
+
Project-URL: Documentation, https://backdraft.dev/docs.html
|
|
7
|
+
Project-URL: Repository, https://github.com/spencerbraun/backdraft
|
|
8
|
+
Project-URL: Roadmap, https://github.com/spencerbraun/backdraft/blob/main/ROADMAP.md
|
|
5
9
|
License: MIT
|
|
6
10
|
License-File: LICENSE
|
|
7
11
|
Requires-Python: >=3.13
|
|
@@ -27,25 +31,26 @@ or does not use the output. Backdraft removes that cost. In the usual loop, an
|
|
|
27
31
|
**agent** does the writing: it reads your source documents only through a gate
|
|
28
32
|
that mints a citation token over every span it shows, cites those tokens
|
|
29
33
|
inline, and a postprocess resolves them, attaches the verbatim evidence, and
|
|
30
|
-
renders one self-contained HTML file
|
|
34
|
+
renders one self-contained HTML file, the memo with the actual cited pages
|
|
31
35
|
and spreadsheet cells embedded inside it. The **human** just reads that file:
|
|
32
36
|
click a claim, see the source.
|
|
33
37
|
|
|
34
38
|
It works the same whether the writer is a model or a person, and whether the
|
|
35
39
|
citations were minted during writing or attached to a document afterwards.
|
|
36
40
|
|
|
37
|
-
Docs live at [backdraft.dev](https://backdraft.dev)
|
|
41
|
+
Docs live at [backdraft.dev](https://backdraft.dev), one page for people, and
|
|
38
42
|
[backdraft.dev/llms.txt](https://backdraft.dev/llms.txt) for agents.
|
|
39
43
|
|
|
40
44
|
## 60 seconds
|
|
41
45
|
|
|
42
46
|
```bash
|
|
43
|
-
uv tool install
|
|
47
|
+
uv tool install "backdraft[vlm]"
|
|
44
48
|
cd demo
|
|
45
49
|
```
|
|
46
50
|
|
|
47
|
-
**Ingest** the sources. Every anchor is minted here
|
|
48
|
-
cells for spreadsheet sheets.
|
|
51
|
+
**Ingest** the sources. Every anchor is minted here, chunks for PDF pages,
|
|
52
|
+
cells for spreadsheet sheets. Formats: PDF, XLSX/XLSM, CSV/TSV, images
|
|
53
|
+
(png, jpeg, tiff, through the vision model), plain text and Markdown.
|
|
49
54
|
|
|
50
55
|
```console
|
|
51
56
|
$ backdraft init
|
|
@@ -66,7 +71,7 @@ $ backdraft read t12-summary p1
|
|
|
66
71
|
# t12-summary p1 (page 1 of 3)
|
|
67
72
|
|
|
68
73
|
[bd:t12-summary:p1.c1:c2e8]
|
|
69
|
-
Bridgeview Commons
|
|
74
|
+
Bridgeview Commons, Trailing Twelve Month Summary
|
|
70
75
|
|
|
71
76
|
Property: Bridgeview Commons, 4400 Halsted Avenue, Columbus, OH 43214. 128 units across four three-story
|
|
72
77
|
garden buildings on 6.2 acres, built in 1998 and substantially renovated between 2019 and 2021. This summary
|
|
@@ -86,7 +91,7 @@ Chunks fall on the document's own paragraphs. A PDF stores glyphs at
|
|
|
86
91
|
coordinates, not paragraphs, so the extractor rebuilds the breaks from the line
|
|
87
92
|
geometry before the chunker sees the page.
|
|
88
93
|
|
|
89
|
-
Search results are citable too
|
|
94
|
+
Search results are citable too, no page read required to get an anchor:
|
|
90
95
|
|
|
91
96
|
```console
|
|
92
97
|
$ backdraft search "24850000"
|
|
@@ -129,12 +134,12 @@ $ echo $?
|
|
|
129
134
|
2
|
|
130
135
|
```
|
|
131
136
|
|
|
132
|
-
Exit 2 means a citation did not resolve
|
|
137
|
+
Exit 2 means a citation did not resolve, the code a `Stop` hook or a CI job
|
|
133
138
|
gates on.
|
|
134
139
|
|
|
135
140
|
**Render.** One self-contained HTML file: the document, the receipts, the
|
|
136
|
-
evidence
|
|
137
|
-
record
|
|
141
|
+
evidence, cited page images, spreadsheet cells in place, the machine-readable
|
|
142
|
+
record, all embedded, no network, nothing to install for the reader.
|
|
138
143
|
|
|
139
144
|
```console
|
|
140
145
|
$ backdraft render memo.md --to html
|
|
@@ -152,13 +157,13 @@ in at [`demo/memo.backdraft.html`](demo/memo.backdraft.html).
|
|
|
152
157
|
|
|
153
158
|
Reading is a **gate**: source documents reach the writer only through `read` and
|
|
154
159
|
`search`, which mint a token over everything they show and record it in a
|
|
155
|
-
**ledger**, so the set of citable things is exactly the set of things shown
|
|
160
|
+
**ledger**, so the set of citable things is exactly the set of things shown, and
|
|
156
161
|
a citation to something the writer never saw is a distinguishable failure rather
|
|
157
162
|
than an invisible one. **Anchors are content-addressed**: a token's identity
|
|
158
163
|
derives from the document's content and a location inside it, and it binds to an
|
|
159
164
|
extraction snapshot, so re-ingesting the same bytes yields the same tokens and a
|
|
160
165
|
changed source yields `drifted` instead of a broken link. **The receipt travels
|
|
161
|
-
with the claim
|
|
166
|
+
with the claim**, an anchor is not a pointer but a pointer plus the verbatim
|
|
162
167
|
snippet and its hash, which is why the finished artifact is defensible with the
|
|
163
168
|
registry deleted and the sources gone. **Verification is a set of switches,
|
|
164
169
|
default off**: resolution is inherent to binding, but `value-trace`, `overlap`
|
|
@@ -175,31 +180,29 @@ artifact. Nothing is ever warned about and dropped.
|
|
|
175
180
|
## What lives where
|
|
176
181
|
|
|
177
182
|
A working directory stays clean: the authored document and its artifact are
|
|
178
|
-
the only visible outputs. Everything else
|
|
179
|
-
records
|
|
183
|
+
the only visible outputs. Everything else, registry, credentials, bind
|
|
184
|
+
records, lives under one hidden `.backdraft/` directory, and
|
|
180
185
|
`backdraft clean` tidies strays from older versions. The registry contains the
|
|
181
186
|
full text of everything ingested, so **gitignore `.backdraft/` for
|
|
182
187
|
confidential corpora**.
|
|
183
188
|
|
|
184
189
|
## Install
|
|
185
190
|
|
|
186
|
-
Not yet on PyPI — install from the repository:
|
|
187
|
-
|
|
188
191
|
```bash
|
|
189
|
-
uv tool install
|
|
190
|
-
uv add
|
|
191
|
-
pip install
|
|
192
|
+
uv tool install "backdraft[vlm]" # CLI, recommended form
|
|
193
|
+
uv add backdraft # as a dependency
|
|
194
|
+
pip install backdraft # or plain pip
|
|
192
195
|
```
|
|
193
196
|
|
|
194
197
|
Python 3.13+. Two extras:
|
|
195
198
|
|
|
196
199
|
| Extra | Adds | For |
|
|
197
200
|
|---|---|---|
|
|
198
|
-
| `[vlm]` | `openai`, `pdf2image` | the VLM extractor
|
|
201
|
+
| `[vlm]` | `openai`, `pdf2image` | the VLM extractor, the recommended path for real PDFs (glossy layouts, info boxes, scans): a vision model reads each page and its clean representation becomes the receipt. `--extractor auto` prefers it only on explicit, backdraft-scoped consent: `BACKDRAFT_VLM_API_KEY` (env or `.backdraft/env`, `backdraft init` writes a template). Ambient `OPENAI_API_KEY`/`OPENROUTER_API_KEY` are deliberately never read; a generic key in the environment is not consent to send documents to its provider. The default provider is OpenRouter running Gemini 3.1 Flash Lite; direct OpenAI is base_url + model, set explicitly. Without a scoped key, `auto` falls back to the keyless text layer and says so. Needs poppler installed separately |
|
|
199
202
|
| `[entail]` | `anthropic` | `bind --check entail`, the model-judge verifier |
|
|
200
203
|
|
|
201
204
|
```bash
|
|
202
|
-
pip install "backdraft[vlm,entail]
|
|
205
|
+
pip install "backdraft[vlm,entail]"
|
|
203
206
|
```
|
|
204
207
|
|
|
205
208
|
From a checkout:
|
|
@@ -212,12 +215,12 @@ uv run pytest
|
|
|
212
215
|
|
|
213
216
|
## Skills
|
|
214
217
|
|
|
215
|
-
Three agent skills ship
|
|
216
|
-
agent's skills directory — for Claude Code, `~/.claude/skills/` (personal) or
|
|
217
|
-
`.claude/skills/` (per project):
|
|
218
|
+
Three agent skills ship inside the package, and the CLI installs them:
|
|
218
219
|
|
|
219
220
|
```bash
|
|
220
|
-
|
|
221
|
+
backdraft skill install # the writing skill, into ~/.claude/skills/
|
|
222
|
+
backdraft skill install --all # plus backfill and artifact-reading
|
|
223
|
+
backdraft skill install --project # into this repo's .claude/skills/
|
|
221
224
|
```
|
|
222
225
|
|
|
223
226
|
| Skill | For |
|
|
@@ -233,11 +236,11 @@ the skills is required to use backdraft by hand.
|
|
|
233
236
|
|
|
234
237
|
| Document | Is |
|
|
235
238
|
|---|---|
|
|
236
|
-
| [DESIGN.md](DESIGN.md) | why it is shaped this way
|
|
237
|
-
| [SPEC.md](SPEC.md) | the builders' contract
|
|
239
|
+
| [DESIGN.md](DESIGN.md) | why it is shaped this way, principles, architecture, decision log |
|
|
240
|
+
| [SPEC.md](SPEC.md) | the builders' contract, types, grammar, DDL, CLI surface, module boundaries |
|
|
238
241
|
| [spec/tokens.md](spec/tokens.md) | the citation token grammar, normatively |
|
|
239
242
|
| [spec/chunking.md](spec/chunking.md) | the deterministic chunker |
|
|
240
|
-
| [spec/artifact.md](spec/artifact.md) | the artifact format
|
|
243
|
+
| [spec/artifact.md](spec/artifact.md) | the artifact format, sidecar payload, legend, HTML rules |
|
|
241
244
|
| [demo/walkthrough.md](demo/walkthrough.md) | the whole thing end to end, with real output |
|
|
242
245
|
|
|
243
246
|
The three files under `spec/` are the portable specification: another
|
|
@@ -246,8 +249,8 @@ implementation reads them and nothing else.
|
|
|
246
249
|
## Status
|
|
247
250
|
|
|
248
251
|
v1. The registry format, the token grammar and the artifact format are pinned
|
|
249
|
-
by prose specs and golden-file tests. What is deliberately queued
|
|
250
|
-
Excel region maps, more formats, `bd:calc` derivations
|
|
252
|
+
by prose specs and golden-file tests. What is deliberately queued, exhibits,
|
|
253
|
+
Excel region maps, more formats, `bd:calc` derivations, is written down in
|
|
251
254
|
[ROADMAP.md](ROADMAP.md).
|
|
252
255
|
|
|
253
256
|
MIT licensed.
|
|
@@ -10,25 +10,26 @@ or does not use the output. Backdraft removes that cost. In the usual loop, an
|
|
|
10
10
|
**agent** does the writing: it reads your source documents only through a gate
|
|
11
11
|
that mints a citation token over every span it shows, cites those tokens
|
|
12
12
|
inline, and a postprocess resolves them, attaches the verbatim evidence, and
|
|
13
|
-
renders one self-contained HTML file
|
|
13
|
+
renders one self-contained HTML file, the memo with the actual cited pages
|
|
14
14
|
and spreadsheet cells embedded inside it. The **human** just reads that file:
|
|
15
15
|
click a claim, see the source.
|
|
16
16
|
|
|
17
17
|
It works the same whether the writer is a model or a person, and whether the
|
|
18
18
|
citations were minted during writing or attached to a document afterwards.
|
|
19
19
|
|
|
20
|
-
Docs live at [backdraft.dev](https://backdraft.dev)
|
|
20
|
+
Docs live at [backdraft.dev](https://backdraft.dev), one page for people, and
|
|
21
21
|
[backdraft.dev/llms.txt](https://backdraft.dev/llms.txt) for agents.
|
|
22
22
|
|
|
23
23
|
## 60 seconds
|
|
24
24
|
|
|
25
25
|
```bash
|
|
26
|
-
uv tool install
|
|
26
|
+
uv tool install "backdraft[vlm]"
|
|
27
27
|
cd demo
|
|
28
28
|
```
|
|
29
29
|
|
|
30
|
-
**Ingest** the sources. Every anchor is minted here
|
|
31
|
-
cells for spreadsheet sheets.
|
|
30
|
+
**Ingest** the sources. Every anchor is minted here, chunks for PDF pages,
|
|
31
|
+
cells for spreadsheet sheets. Formats: PDF, XLSX/XLSM, CSV/TSV, images
|
|
32
|
+
(png, jpeg, tiff, through the vision model), plain text and Markdown.
|
|
32
33
|
|
|
33
34
|
```console
|
|
34
35
|
$ backdraft init
|
|
@@ -49,7 +50,7 @@ $ backdraft read t12-summary p1
|
|
|
49
50
|
# t12-summary p1 (page 1 of 3)
|
|
50
51
|
|
|
51
52
|
[bd:t12-summary:p1.c1:c2e8]
|
|
52
|
-
Bridgeview Commons
|
|
53
|
+
Bridgeview Commons, Trailing Twelve Month Summary
|
|
53
54
|
|
|
54
55
|
Property: Bridgeview Commons, 4400 Halsted Avenue, Columbus, OH 43214. 128 units across four three-story
|
|
55
56
|
garden buildings on 6.2 acres, built in 1998 and substantially renovated between 2019 and 2021. This summary
|
|
@@ -69,7 +70,7 @@ Chunks fall on the document's own paragraphs. A PDF stores glyphs at
|
|
|
69
70
|
coordinates, not paragraphs, so the extractor rebuilds the breaks from the line
|
|
70
71
|
geometry before the chunker sees the page.
|
|
71
72
|
|
|
72
|
-
Search results are citable too
|
|
73
|
+
Search results are citable too, no page read required to get an anchor:
|
|
73
74
|
|
|
74
75
|
```console
|
|
75
76
|
$ backdraft search "24850000"
|
|
@@ -112,12 +113,12 @@ $ echo $?
|
|
|
112
113
|
2
|
|
113
114
|
```
|
|
114
115
|
|
|
115
|
-
Exit 2 means a citation did not resolve
|
|
116
|
+
Exit 2 means a citation did not resolve, the code a `Stop` hook or a CI job
|
|
116
117
|
gates on.
|
|
117
118
|
|
|
118
119
|
**Render.** One self-contained HTML file: the document, the receipts, the
|
|
119
|
-
evidence
|
|
120
|
-
record
|
|
120
|
+
evidence, cited page images, spreadsheet cells in place, the machine-readable
|
|
121
|
+
record, all embedded, no network, nothing to install for the reader.
|
|
121
122
|
|
|
122
123
|
```console
|
|
123
124
|
$ backdraft render memo.md --to html
|
|
@@ -135,13 +136,13 @@ in at [`demo/memo.backdraft.html`](demo/memo.backdraft.html).
|
|
|
135
136
|
|
|
136
137
|
Reading is a **gate**: source documents reach the writer only through `read` and
|
|
137
138
|
`search`, which mint a token over everything they show and record it in a
|
|
138
|
-
**ledger**, so the set of citable things is exactly the set of things shown
|
|
139
|
+
**ledger**, so the set of citable things is exactly the set of things shown, and
|
|
139
140
|
a citation to something the writer never saw is a distinguishable failure rather
|
|
140
141
|
than an invisible one. **Anchors are content-addressed**: a token's identity
|
|
141
142
|
derives from the document's content and a location inside it, and it binds to an
|
|
142
143
|
extraction snapshot, so re-ingesting the same bytes yields the same tokens and a
|
|
143
144
|
changed source yields `drifted` instead of a broken link. **The receipt travels
|
|
144
|
-
with the claim
|
|
145
|
+
with the claim**, an anchor is not a pointer but a pointer plus the verbatim
|
|
145
146
|
snippet and its hash, which is why the finished artifact is defensible with the
|
|
146
147
|
registry deleted and the sources gone. **Verification is a set of switches,
|
|
147
148
|
default off**: resolution is inherent to binding, but `value-trace`, `overlap`
|
|
@@ -158,31 +159,29 @@ artifact. Nothing is ever warned about and dropped.
|
|
|
158
159
|
## What lives where
|
|
159
160
|
|
|
160
161
|
A working directory stays clean: the authored document and its artifact are
|
|
161
|
-
the only visible outputs. Everything else
|
|
162
|
-
records
|
|
162
|
+
the only visible outputs. Everything else, registry, credentials, bind
|
|
163
|
+
records, lives under one hidden `.backdraft/` directory, and
|
|
163
164
|
`backdraft clean` tidies strays from older versions. The registry contains the
|
|
164
165
|
full text of everything ingested, so **gitignore `.backdraft/` for
|
|
165
166
|
confidential corpora**.
|
|
166
167
|
|
|
167
168
|
## Install
|
|
168
169
|
|
|
169
|
-
Not yet on PyPI — install from the repository:
|
|
170
|
-
|
|
171
170
|
```bash
|
|
172
|
-
uv tool install
|
|
173
|
-
uv add
|
|
174
|
-
pip install
|
|
171
|
+
uv tool install "backdraft[vlm]" # CLI, recommended form
|
|
172
|
+
uv add backdraft # as a dependency
|
|
173
|
+
pip install backdraft # or plain pip
|
|
175
174
|
```
|
|
176
175
|
|
|
177
176
|
Python 3.13+. Two extras:
|
|
178
177
|
|
|
179
178
|
| Extra | Adds | For |
|
|
180
179
|
|---|---|---|
|
|
181
|
-
| `[vlm]` | `openai`, `pdf2image` | the VLM extractor
|
|
180
|
+
| `[vlm]` | `openai`, `pdf2image` | the VLM extractor, the recommended path for real PDFs (glossy layouts, info boxes, scans): a vision model reads each page and its clean representation becomes the receipt. `--extractor auto` prefers it only on explicit, backdraft-scoped consent: `BACKDRAFT_VLM_API_KEY` (env or `.backdraft/env`, `backdraft init` writes a template). Ambient `OPENAI_API_KEY`/`OPENROUTER_API_KEY` are deliberately never read; a generic key in the environment is not consent to send documents to its provider. The default provider is OpenRouter running Gemini 3.1 Flash Lite; direct OpenAI is base_url + model, set explicitly. Without a scoped key, `auto` falls back to the keyless text layer and says so. Needs poppler installed separately |
|
|
182
181
|
| `[entail]` | `anthropic` | `bind --check entail`, the model-judge verifier |
|
|
183
182
|
|
|
184
183
|
```bash
|
|
185
|
-
pip install "backdraft[vlm,entail]
|
|
184
|
+
pip install "backdraft[vlm,entail]"
|
|
186
185
|
```
|
|
187
186
|
|
|
188
187
|
From a checkout:
|
|
@@ -195,12 +194,12 @@ uv run pytest
|
|
|
195
194
|
|
|
196
195
|
## Skills
|
|
197
196
|
|
|
198
|
-
Three agent skills ship
|
|
199
|
-
agent's skills directory — for Claude Code, `~/.claude/skills/` (personal) or
|
|
200
|
-
`.claude/skills/` (per project):
|
|
197
|
+
Three agent skills ship inside the package, and the CLI installs them:
|
|
201
198
|
|
|
202
199
|
```bash
|
|
203
|
-
|
|
200
|
+
backdraft skill install # the writing skill, into ~/.claude/skills/
|
|
201
|
+
backdraft skill install --all # plus backfill and artifact-reading
|
|
202
|
+
backdraft skill install --project # into this repo's .claude/skills/
|
|
204
203
|
```
|
|
205
204
|
|
|
206
205
|
| Skill | For |
|
|
@@ -216,11 +215,11 @@ the skills is required to use backdraft by hand.
|
|
|
216
215
|
|
|
217
216
|
| Document | Is |
|
|
218
217
|
|---|---|
|
|
219
|
-
| [DESIGN.md](DESIGN.md) | why it is shaped this way
|
|
220
|
-
| [SPEC.md](SPEC.md) | the builders' contract
|
|
218
|
+
| [DESIGN.md](DESIGN.md) | why it is shaped this way, principles, architecture, decision log |
|
|
219
|
+
| [SPEC.md](SPEC.md) | the builders' contract, types, grammar, DDL, CLI surface, module boundaries |
|
|
221
220
|
| [spec/tokens.md](spec/tokens.md) | the citation token grammar, normatively |
|
|
222
221
|
| [spec/chunking.md](spec/chunking.md) | the deterministic chunker |
|
|
223
|
-
| [spec/artifact.md](spec/artifact.md) | the artifact format
|
|
222
|
+
| [spec/artifact.md](spec/artifact.md) | the artifact format, sidecar payload, legend, HTML rules |
|
|
224
223
|
| [demo/walkthrough.md](demo/walkthrough.md) | the whole thing end to end, with real output |
|
|
225
224
|
|
|
226
225
|
The three files under `spec/` are the portable specification: another
|
|
@@ -229,8 +228,8 @@ implementation reads them and nothing else.
|
|
|
229
228
|
## Status
|
|
230
229
|
|
|
231
230
|
v1. The registry format, the token grammar and the artifact format are pinned
|
|
232
|
-
by prose specs and golden-file tests. What is deliberately queued
|
|
233
|
-
Excel region maps, more formats, `bd:calc` derivations
|
|
231
|
+
by prose specs and golden-file tests. What is deliberately queued, exhibits,
|
|
232
|
+
Excel region maps, more formats, `bd:calc` derivations, is written down in
|
|
234
233
|
[ROADMAP.md](ROADMAP.md).
|
|
235
234
|
|
|
236
235
|
MIT licensed.
|
|
@@ -14,7 +14,7 @@ losing it. Items graduate by getting built and deleted.
|
|
|
14
14
|
2026-07-28 pending a real audience for it.*
|
|
15
15
|
|
|
16
16
|
- **Excel region maps and range reads** — navigation for large sheets. The
|
|
17
|
-
research is done (
|
|
17
|
+
research is done (notes kept privately): a
|
|
18
18
|
deterministic detection cascade (declared tables → named ranges → print
|
|
19
19
|
areas / freeze panes → merged-aware connected components) producing a
|
|
20
20
|
table of contents per sheet. Two hard constraints from that research:
|
|
@@ -23,24 +23,22 @@ losing it. Items graduate by getting built and deleted.
|
|
|
23
23
|
|
|
24
24
|
## Formats
|
|
25
25
|
|
|
26
|
-
Today: PDF (VLM primary, text-layer floor), XLSX,
|
|
27
|
-
|
|
28
|
-
|
|
26
|
+
Today: PDF (VLM primary, text-layer floor), XLSX/XLSM, CSV/TSV, images
|
|
27
|
+
(png, jpeg, tiff — VLM, the file is its own snapshot), plain text and
|
|
28
|
+
Markdown. Each new format is one Extractor implementing the existing
|
|
29
|
+
protocol — pages out, receipts quotable, nothing downstream changes.
|
|
29
30
|
|
|
30
|
-
- **Images (png, jpeg, tiff)** — a photographed or scanned page is a
|
|
31
|
-
one-page document; the VLM extractor already is this pipeline minus the
|
|
32
|
-
pdf2image step, and the source file is its own stored snapshot. Cheapest
|
|
33
|
-
high-value addition.
|
|
34
|
-
- **CSV** — a workbook with one sheet; reuse the sheet representation (A1
|
|
35
|
-
refs, cell anchors) wholesale. Deterministic, keyless, near-trivial.
|
|
36
31
|
- **DOCX** — leases, LOIs, agreements. Structured XML, so the deterministic
|
|
37
32
|
path is the good path (paragraphs and tables straight out of the file, no
|
|
38
|
-
OCR); tables render to markdown like sheets do.
|
|
33
|
+
OCR); tables render to markdown like sheets do. Needs a decision row
|
|
34
|
+
first: DOCX has no pages, so locators want synthesizing from heading
|
|
35
|
+
sections.
|
|
39
36
|
- **PPTX** — offering memoranda that arrive as decks. Slide text is
|
|
40
|
-
extractable deterministically as the floor; the faithful path
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
37
|
+
extractable deterministically as the floor; the faithful path (slide
|
|
38
|
+
images) needs a renderer on PATH, LibreOffice or similar, behind a
|
|
39
|
+
capability check like poppler's.
|
|
40
|
+
- **XLS** — legacy workbooks; python-calamine as the tolerant fallback
|
|
41
|
+
behind an extra.
|
|
44
42
|
- **Someday**: HTML pages, email (.eml) — both arrive in diligence folders,
|
|
45
43
|
both have messy identity questions (what is "the source" of a web page?)
|
|
46
44
|
that deserve a decision row before code.
|
|
@@ -52,10 +50,11 @@ out, receipts quotable, nothing downstream changes.
|
|
|
52
50
|
malformed-on-purpose). The field-trial case: "about 4% of embedded
|
|
53
51
|
loss-to-lease," derived from two cited cells, currently an honest
|
|
54
52
|
value-trace fail with nowhere to point.
|
|
55
|
-
- **
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
53
|
+
- **Merged-cell and frozen-pane rendering** — ingest already captures
|
|
54
|
+
`merged` and `frozen` in sheet meta; the artifact's sheet view renders
|
|
55
|
+
neither yet (merges want colspan/rowspan grid building, frozen panes want
|
|
56
|
+
a second sticky axis). Styling, number formats, and column widths already
|
|
57
|
+
render.
|
|
59
58
|
- **Living documents** — re-ingest, re-bind, and a drift-first report: "these
|
|
60
59
|
claims cite figures that changed; cited-then vs. now." The primitives
|
|
61
60
|
(generations, `drifted`, the word-diff) all exist; the missing piece is a
|
|
@@ -64,8 +63,8 @@ out, receipts quotable, nothing downstream changes.
|
|
|
64
63
|
and `--to json` contracts are already shaped for it), and a client-side
|
|
65
64
|
drag-drop viewer page (no upload — the file never leaves the reader's
|
|
66
65
|
machine).
|
|
67
|
-
- **Distribution** —
|
|
68
|
-
|
|
66
|
+
- **Distribution** — published on PyPI; still queued: a skills-registry
|
|
67
|
+
listing when one becomes the standard channel.
|
|
69
68
|
|
|
70
69
|
## Someday
|
|
71
70
|
|