backdraft 0.2.0__tar.gz → 0.2.1__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.
Files changed (128) hide show
  1. {backdraft-0.2.0 → backdraft-0.2.1}/DESIGN.md +2 -2
  2. {backdraft-0.2.0 → backdraft-0.2.1}/PKG-INFO +25 -27
  3. {backdraft-0.2.0 → backdraft-0.2.1}/README.md +24 -26
  4. {backdraft-0.2.0 → backdraft-0.2.1}/pyproject.toml +4 -1
  5. {backdraft-0.2.0 → backdraft-0.2.1}/site/docs.html +40 -28
  6. {backdraft-0.2.0 → backdraft-0.2.1}/site/index.html +203 -179
  7. {backdraft-0.2.0 → backdraft-0.2.1}/site/llms.txt +5 -5
  8. {backdraft-0.2.0 → backdraft-0.2.1}/src/backdraft/cli.py +53 -0
  9. {backdraft-0.2.0 → backdraft-0.2.1}/uv.lock +1 -1
  10. {backdraft-0.2.0 → backdraft-0.2.1}/.gitignore +0 -0
  11. {backdraft-0.2.0 → backdraft-0.2.1}/.python-version +0 -0
  12. {backdraft-0.2.0 → backdraft-0.2.1}/LICENSE +0 -0
  13. {backdraft-0.2.0 → backdraft-0.2.1}/ROADMAP.md +0 -0
  14. {backdraft-0.2.0 → backdraft-0.2.1}/SPEC.md +0 -0
  15. {backdraft-0.2.0 → backdraft-0.2.1}/assets/backdraft-mark.svg +0 -0
  16. {backdraft-0.2.0 → backdraft-0.2.1}/demo/generate_sources.py +0 -0
  17. {backdraft-0.2.0 → backdraft-0.2.1}/demo/memo.backdraft.html +0 -0
  18. {backdraft-0.2.0 → backdraft-0.2.1}/demo/memo.md +0 -0
  19. {backdraft-0.2.0 → backdraft-0.2.1}/demo/sources/t12-summary.pdf +0 -0
  20. {backdraft-0.2.0 → backdraft-0.2.1}/demo/sources/underwriting-model.xlsx +0 -0
  21. {backdraft-0.2.0 → backdraft-0.2.1}/demo/walkthrough.md +0 -0
  22. {backdraft-0.2.0 → backdraft-0.2.1}/research/20260727-excel-region-detection.md +0 -0
  23. {backdraft-0.2.0 → backdraft-0.2.1}/research/20260727-research-findings.md +0 -0
  24. {backdraft-0.2.0 → backdraft-0.2.1}/site/demo.html +0 -0
  25. {backdraft-0.2.0 → backdraft-0.2.1}/skills/backdraft/SKILL.md +0 -0
  26. {backdraft-0.2.0 → backdraft-0.2.1}/skills/backdraft-artifact/SKILL.md +0 -0
  27. {backdraft-0.2.0 → backdraft-0.2.1}/skills/backdraft-backfill/SKILL.md +0 -0
  28. {backdraft-0.2.0 → backdraft-0.2.1}/spec/artifact.md +0 -0
  29. {backdraft-0.2.0 → backdraft-0.2.1}/spec/chunking.md +0 -0
  30. {backdraft-0.2.0 → backdraft-0.2.1}/spec/tokens.md +0 -0
  31. {backdraft-0.2.0 → backdraft-0.2.1}/src/backdraft/__init__.py +0 -0
  32. {backdraft-0.2.0 → backdraft-0.2.1}/src/backdraft/bind/__init__.py +0 -0
  33. {backdraft-0.2.0 → backdraft-0.2.1}/src/backdraft/bind/binder.py +0 -0
  34. {backdraft-0.2.0 → backdraft-0.2.1}/src/backdraft/bind/cli.py +0 -0
  35. {backdraft-0.2.0 → backdraft-0.2.1}/src/backdraft/bind/evidence.py +0 -0
  36. {backdraft-0.2.0 → backdraft-0.2.1}/src/backdraft/bind/verify/__init__.py +0 -0
  37. {backdraft-0.2.0 → backdraft-0.2.1}/src/backdraft/bind/verify/base.py +0 -0
  38. {backdraft-0.2.0 → backdraft-0.2.1}/src/backdraft/bind/verify/entail.py +0 -0
  39. {backdraft-0.2.0 → backdraft-0.2.1}/src/backdraft/bind/verify/overlap.py +0 -0
  40. {backdraft-0.2.0 → backdraft-0.2.1}/src/backdraft/bind/verify/recompute.py +0 -0
  41. {backdraft-0.2.0 → backdraft-0.2.1}/src/backdraft/bind/verify/value_trace.py +0 -0
  42. {backdraft-0.2.0 → backdraft-0.2.1}/src/backdraft/cli_context.py +0 -0
  43. {backdraft-0.2.0 → backdraft-0.2.1}/src/backdraft/credentials.py +0 -0
  44. {backdraft-0.2.0 → backdraft-0.2.1}/src/backdraft/extract/__init__.py +0 -0
  45. {backdraft-0.2.0 → backdraft-0.2.1}/src/backdraft/extract/base.py +0 -0
  46. {backdraft-0.2.0 → backdraft-0.2.1}/src/backdraft/extract/pdf_text.py +0 -0
  47. {backdraft-0.2.0 → backdraft-0.2.1}/src/backdraft/extract/text.py +0 -0
  48. {backdraft-0.2.0 → backdraft-0.2.1}/src/backdraft/extract/vlm.py +0 -0
  49. {backdraft-0.2.0 → backdraft-0.2.1}/src/backdraft/extract/vlm_settings.py +0 -0
  50. {backdraft-0.2.0 → backdraft-0.2.1}/src/backdraft/extract/xlsx.py +0 -0
  51. {backdraft-0.2.0 → backdraft-0.2.1}/src/backdraft/gate/__init__.py +0 -0
  52. {backdraft-0.2.0 → backdraft-0.2.1}/src/backdraft/gate/cli.py +0 -0
  53. {backdraft-0.2.0 → backdraft-0.2.1}/src/backdraft/gate/reader.py +0 -0
  54. {backdraft-0.2.0 → backdraft-0.2.1}/src/backdraft/gate/searcher.py +0 -0
  55. {backdraft-0.2.0 → backdraft-0.2.1}/src/backdraft/kernel/__init__.py +0 -0
  56. {backdraft-0.2.0 → backdraft-0.2.1}/src/backdraft/kernel/artifact.py +0 -0
  57. {backdraft-0.2.0 → backdraft-0.2.1}/src/backdraft/kernel/chunking.py +0 -0
  58. {backdraft-0.2.0 → backdraft-0.2.1}/src/backdraft/kernel/claims.py +0 -0
  59. {backdraft-0.2.0 → backdraft-0.2.1}/src/backdraft/kernel/errors.py +0 -0
  60. {backdraft-0.2.0 → backdraft-0.2.1}/src/backdraft/kernel/hashing.py +0 -0
  61. {backdraft-0.2.0 → backdraft-0.2.1}/src/backdraft/kernel/model.py +0 -0
  62. {backdraft-0.2.0 → backdraft-0.2.1}/src/backdraft/kernel/tokens.py +0 -0
  63. {backdraft-0.2.0 → backdraft-0.2.1}/src/backdraft/registry/__init__.py +0 -0
  64. {backdraft-0.2.0 → backdraft-0.2.1}/src/backdraft/registry/ledger.py +0 -0
  65. {backdraft-0.2.0 → backdraft-0.2.1}/src/backdraft/registry/schema.sql +0 -0
  66. {backdraft-0.2.0 → backdraft-0.2.1}/src/backdraft/registry/store.py +0 -0
  67. {backdraft-0.2.0 → backdraft-0.2.1}/src/backdraft/render/__init__.py +0 -0
  68. {backdraft-0.2.0 → backdraft-0.2.1}/src/backdraft/render/_text.py +0 -0
  69. {backdraft-0.2.0 → backdraft-0.2.1}/src/backdraft/render/cli.py +0 -0
  70. {backdraft-0.2.0 → backdraft-0.2.1}/src/backdraft/render/footnotes.py +0 -0
  71. {backdraft-0.2.0 → backdraft-0.2.1}/src/backdraft/render/html.py +0 -0
  72. {backdraft-0.2.0 → backdraft-0.2.1}/src/backdraft/render/markdown.py +0 -0
  73. {backdraft-0.2.0 → backdraft-0.2.1}/src/backdraft/render/placement.py +0 -0
  74. {backdraft-0.2.0 → backdraft-0.2.1}/src/backdraft/render/sidecar.py +0 -0
  75. {backdraft-0.2.0 → backdraft-0.2.1}/tests/conftest.py +0 -0
  76. {backdraft-0.2.0 → backdraft-0.2.1}/tests/conftest_registry.py +0 -0
  77. {backdraft-0.2.0 → backdraft-0.2.1}/tests/conftest_render.py +0 -0
  78. {backdraft-0.2.0 → backdraft-0.2.1}/tests/fakes.py +0 -0
  79. {backdraft-0.2.0 → backdraft-0.2.1}/tests/gate/conftest.py +0 -0
  80. {backdraft-0.2.0 → backdraft-0.2.1}/tests/gate/fake_registry.py +0 -0
  81. {backdraft-0.2.0 → backdraft-0.2.1}/tests/gate/test_gate_cli.py +0 -0
  82. {backdraft-0.2.0 → backdraft-0.2.1}/tests/gate/test_gate_ledger.py +0 -0
  83. {backdraft-0.2.0 → backdraft-0.2.1}/tests/gate/test_gate_reader.py +0 -0
  84. {backdraft-0.2.0 → backdraft-0.2.1}/tests/gate/test_gate_searcher.py +0 -0
  85. {backdraft-0.2.0 → backdraft-0.2.1}/tests/gate/test_reader_cells.py +0 -0
  86. {backdraft-0.2.0 → backdraft-0.2.1}/tests/golden/bind/backfill.bound.md +0 -0
  87. {backdraft-0.2.0 → backdraft-0.2.1}/tests/golden/bind/frontwalk.bound.md +0 -0
  88. {backdraft-0.2.0 → backdraft-0.2.1}/tests/golden/chunking/long_tail.json +0 -0
  89. {backdraft-0.2.0 → backdraft-0.2.1}/tests/golden/chunking/long_tail.txt +0 -0
  90. {backdraft-0.2.0 → backdraft-0.2.1}/tests/golden/chunking/many_small.json +0 -0
  91. {backdraft-0.2.0 → backdraft-0.2.1}/tests/golden/chunking/many_small.txt +0 -0
  92. {backdraft-0.2.0 → backdraft-0.2.1}/tests/golden/chunking/mixed.json +0 -0
  93. {backdraft-0.2.0 → backdraft-0.2.1}/tests/golden/chunking/mixed.txt +0 -0
  94. {backdraft-0.2.0 → backdraft-0.2.1}/tests/golden/chunking/one_giant.json +0 -0
  95. {backdraft-0.2.0 → backdraft-0.2.1}/tests/golden/chunking/one_giant.txt +0 -0
  96. {backdraft-0.2.0 → backdraft-0.2.1}/tests/golden/chunking/short_page.json +0 -0
  97. {backdraft-0.2.0 → backdraft-0.2.1}/tests/golden/chunking/short_page.txt +0 -0
  98. {backdraft-0.2.0 → backdraft-0.2.1}/tests/golden/chunking/unsplittable.json +0 -0
  99. {backdraft-0.2.0 → backdraft-0.2.1}/tests/golden/chunking/unsplittable.txt +0 -0
  100. {backdraft-0.2.0 → backdraft-0.2.1}/tests/golden/render/backfill.footnotes.md +0 -0
  101. {backdraft-0.2.0 → backdraft-0.2.1}/tests/golden/render/backfill.sidecar.json +0 -0
  102. {backdraft-0.2.0 → backdraft-0.2.1}/tests/golden/render/demo.footnotes.md +0 -0
  103. {backdraft-0.2.0 → backdraft-0.2.1}/tests/golden/render/demo.sidecar.json +0 -0
  104. {backdraft-0.2.0 → backdraft-0.2.1}/tests/golden_util.py +0 -0
  105. {backdraft-0.2.0 → backdraft-0.2.1}/tests/test_bind_binder.py +0 -0
  106. {backdraft-0.2.0 → backdraft-0.2.1}/tests/test_bind_cli.py +0 -0
  107. {backdraft-0.2.0 → backdraft-0.2.1}/tests/test_bind_evidence.py +0 -0
  108. {backdraft-0.2.0 → backdraft-0.2.1}/tests/test_bind_verify.py +0 -0
  109. {backdraft-0.2.0 → backdraft-0.2.1}/tests/test_chunking.py +0 -0
  110. {backdraft-0.2.0 → backdraft-0.2.1}/tests/test_claims.py +0 -0
  111. {backdraft-0.2.0 → backdraft-0.2.1}/tests/test_cli.py +0 -0
  112. {backdraft-0.2.0 → backdraft-0.2.1}/tests/test_credentials.py +0 -0
  113. {backdraft-0.2.0 → backdraft-0.2.1}/tests/test_extract.py +0 -0
  114. {backdraft-0.2.0 → backdraft-0.2.1}/tests/test_footnotes.py +0 -0
  115. {backdraft-0.2.0 → backdraft-0.2.1}/tests/test_gate_integration.py +0 -0
  116. {backdraft-0.2.0 → backdraft-0.2.1}/tests/test_hashing.py +0 -0
  117. {backdraft-0.2.0 → backdraft-0.2.1}/tests/test_html.py +0 -0
  118. {backdraft-0.2.0 → backdraft-0.2.1}/tests/test_invariants.py +0 -0
  119. {backdraft-0.2.0 → backdraft-0.2.1}/tests/test_model.py +0 -0
  120. {backdraft-0.2.0 → backdraft-0.2.1}/tests/test_placement.py +0 -0
  121. {backdraft-0.2.0 → backdraft-0.2.1}/tests/test_records_location.py +0 -0
  122. {backdraft-0.2.0 → backdraft-0.2.1}/tests/test_registry.py +0 -0
  123. {backdraft-0.2.0 → backdraft-0.2.1}/tests/test_reingest.py +0 -0
  124. {backdraft-0.2.0 → backdraft-0.2.1}/tests/test_render_cli.py +0 -0
  125. {backdraft-0.2.0 → backdraft-0.2.1}/tests/test_render_markdown.py +0 -0
  126. {backdraft-0.2.0 → backdraft-0.2.1}/tests/test_sidecar.py +0 -0
  127. {backdraft-0.2.0 → backdraft-0.2.1}/tests/test_tokens.py +0 -0
  128. {backdraft-0.2.0 → backdraft-0.2.1}/tests/test_value_trace.py +0 -0
@@ -140,7 +140,7 @@ substrates); this list is questions still awaiting a decision:
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
142
  truncation from the Darnell trial lives here)
143
- - Distribution: PyPI (name `backdraft` verified free 2026-07-28), skill
144
- registration; domain backdraft.dev acquired 2026-07-28
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
145
  - First external user (LoanCore validation layer vs Clarion eval substrate vs
146
146
  open release feedback)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: backdraft
3
- Version: 0.2.0
3
+ Version: 0.2.1
4
4
  Summary: Drop-in provenance for factual claims: one click from any claim to the evidence behind it.
5
5
  License: MIT
6
6
  License-File: LICENSE
@@ -27,24 +27,24 @@ or does not use the output. Backdraft removes that cost. In the usual loop, an
27
27
  **agent** does the writing: it reads your source documents only through a gate
28
28
  that mints a citation token over every span it shows, cites those tokens
29
29
  inline, and a postprocess resolves them, attaches the verbatim evidence, and
30
- renders one self-contained HTML file — the memo with the actual cited pages
30
+ renders one self-contained HTML file, the memo with the actual cited pages
31
31
  and spreadsheet cells embedded inside it. The **human** just reads that file:
32
32
  click a claim, see the source.
33
33
 
34
34
  It works the same whether the writer is a model or a person, and whether the
35
35
  citations were minted during writing or attached to a document afterwards.
36
36
 
37
- Docs live at [backdraft.dev](https://backdraft.dev) — one page for people, and
37
+ Docs live at [backdraft.dev](https://backdraft.dev), one page for people, and
38
38
  [backdraft.dev/llms.txt](https://backdraft.dev/llms.txt) for agents.
39
39
 
40
40
  ## 60 seconds
41
41
 
42
42
  ```bash
43
- uv tool install git+https://github.com/spencerbraun/backdraft
43
+ uv tool install "backdraft[vlm]"
44
44
  cd demo
45
45
  ```
46
46
 
47
- **Ingest** the sources. Every anchor is minted here — chunks for PDF pages,
47
+ **Ingest** the sources. Every anchor is minted here, chunks for PDF pages,
48
48
  cells for spreadsheet sheets.
49
49
 
50
50
  ```console
@@ -66,7 +66,7 @@ $ backdraft read t12-summary p1
66
66
  # t12-summary p1 (page 1 of 3)
67
67
 
68
68
  [bd:t12-summary:p1.c1:c2e8]
69
- Bridgeview Commons — Trailing Twelve Month Summary
69
+ Bridgeview Commons, Trailing Twelve Month Summary
70
70
 
71
71
  Property: Bridgeview Commons, 4400 Halsted Avenue, Columbus, OH 43214. 128 units across four three-story
72
72
  garden buildings on 6.2 acres, built in 1998 and substantially renovated between 2019 and 2021. This summary
@@ -86,7 +86,7 @@ Chunks fall on the document's own paragraphs. A PDF stores glyphs at
86
86
  coordinates, not paragraphs, so the extractor rebuilds the breaks from the line
87
87
  geometry before the chunker sees the page.
88
88
 
89
- Search results are citable too — no page read required to get an anchor:
89
+ Search results are citable too, no page read required to get an anchor:
90
90
 
91
91
  ```console
92
92
  $ backdraft search "24850000"
@@ -129,12 +129,12 @@ $ echo $?
129
129
  2
130
130
  ```
131
131
 
132
- Exit 2 means a citation did not resolve — the code a `Stop` hook or a CI job
132
+ Exit 2 means a citation did not resolve, the code a `Stop` hook or a CI job
133
133
  gates on.
134
134
 
135
135
  **Render.** One self-contained HTML file: the document, the receipts, the
136
- evidence — cited page images, spreadsheet cells in place, the machine-readable
137
- record — all embedded, no network, nothing to install for the reader.
136
+ evidence, cited page images, spreadsheet cells in place, the machine-readable
137
+ record, all embedded, no network, nothing to install for the reader.
138
138
 
139
139
  ```console
140
140
  $ backdraft render memo.md --to html
@@ -152,13 +152,13 @@ in at [`demo/memo.backdraft.html`](demo/memo.backdraft.html).
152
152
 
153
153
  Reading is a **gate**: source documents reach the writer only through `read` and
154
154
  `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 — and
155
+ **ledger**, so the set of citable things is exactly the set of things shown, and
156
156
  a citation to something the writer never saw is a distinguishable failure rather
157
157
  than an invisible one. **Anchors are content-addressed**: a token's identity
158
158
  derives from the document's content and a location inside it, and it binds to an
159
159
  extraction snapshot, so re-ingesting the same bytes yields the same tokens and a
160
160
  changed source yields `drifted` instead of a broken link. **The receipt travels
161
- with the claim** — an anchor is not a pointer but a pointer plus the verbatim
161
+ with the claim**, an anchor is not a pointer but a pointer plus the verbatim
162
162
  snippet and its hash, which is why the finished artifact is defensible with the
163
163
  registry deleted and the sources gone. **Verification is a set of switches,
164
164
  default off**: resolution is inherent to binding, but `value-trace`, `overlap`
@@ -175,31 +175,29 @@ artifact. Nothing is ever warned about and dropped.
175
175
  ## What lives where
176
176
 
177
177
  A working directory stays clean: the authored document and its artifact are
178
- the only visible outputs. Everything else — registry, credentials, bind
179
- records — lives under one hidden `.backdraft/` directory, and
178
+ the only visible outputs. Everything else, registry, credentials, bind
179
+ records, lives under one hidden `.backdraft/` directory, and
180
180
  `backdraft clean` tidies strays from older versions. The registry contains the
181
181
  full text of everything ingested, so **gitignore `.backdraft/` for
182
182
  confidential corpora**.
183
183
 
184
184
  ## Install
185
185
 
186
- Not yet on PyPI — install from the repository:
187
-
188
186
  ```bash
189
- uv tool install git+https://github.com/spencerbraun/backdraft # CLI
190
- uv add "backdraft @ git+https://github.com/spencerbraun/backdraft" # as a dependency
191
- pip install git+https://github.com/spencerbraun/backdraft # or plain pip
187
+ uv tool install "backdraft[vlm]" # CLI, recommended form
188
+ uv add backdraft # as a dependency
189
+ pip install backdraft # or plain pip
192
190
  ```
193
191
 
194
192
  Python 3.13+. Two extras:
195
193
 
196
194
  | Extra | Adds | For |
197
195
  |---|---|---|
198
- | `[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 |
196
+ | `[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
197
  | `[entail]` | `anthropic` | `bind --check entail`, the model-judge verifier |
200
198
 
201
199
  ```bash
202
- pip install "backdraft[vlm,entail] @ git+https://github.com/spencerbraun/backdraft"
200
+ pip install "backdraft[vlm,entail]"
203
201
  ```
204
202
 
205
203
  From a checkout:
@@ -213,7 +211,7 @@ uv run pytest
213
211
  ## Skills
214
212
 
215
213
  Three agent skills ship in [`skills/`](skills). Copy the ones you want into your
216
- agent's skills directory — for Claude Code, `~/.claude/skills/` (personal) or
214
+ agent's skills directory, for Claude Code, `~/.claude/skills/` (personal) or
217
215
  `.claude/skills/` (per project):
218
216
 
219
217
  ```bash
@@ -233,11 +231,11 @@ the skills is required to use backdraft by hand.
233
231
 
234
232
  | Document | Is |
235
233
  |---|---|
236
- | [DESIGN.md](DESIGN.md) | why it is shaped this way — principles, architecture, decision log |
237
- | [SPEC.md](SPEC.md) | the builders' contract — types, grammar, DDL, CLI surface, module boundaries |
234
+ | [DESIGN.md](DESIGN.md) | why it is shaped this way, principles, architecture, decision log |
235
+ | [SPEC.md](SPEC.md) | the builders' contract, types, grammar, DDL, CLI surface, module boundaries |
238
236
  | [spec/tokens.md](spec/tokens.md) | the citation token grammar, normatively |
239
237
  | [spec/chunking.md](spec/chunking.md) | the deterministic chunker |
240
- | [spec/artifact.md](spec/artifact.md) | the artifact format — sidecar payload, legend, HTML rules |
238
+ | [spec/artifact.md](spec/artifact.md) | the artifact format, sidecar payload, legend, HTML rules |
241
239
  | [demo/walkthrough.md](demo/walkthrough.md) | the whole thing end to end, with real output |
242
240
 
243
241
  The three files under `spec/` are the portable specification: another
@@ -246,8 +244,8 @@ implementation reads them and nothing else.
246
244
  ## Status
247
245
 
248
246
  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 — exhibits,
250
- Excel region maps, more formats, `bd:calc` derivations — is written down in
247
+ by prose specs and golden-file tests. What is deliberately queued, exhibits,
248
+ Excel region maps, more formats, `bd:calc` derivations, is written down in
251
249
  [ROADMAP.md](ROADMAP.md).
252
250
 
253
251
  MIT licensed.
@@ -10,24 +10,24 @@ 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 — the memo with the actual cited pages
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) — one page for people, and
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 git+https://github.com/spencerbraun/backdraft
26
+ uv tool install "backdraft[vlm]"
27
27
  cd demo
28
28
  ```
29
29
 
30
- **Ingest** the sources. Every anchor is minted here — chunks for PDF pages,
30
+ **Ingest** the sources. Every anchor is minted here, chunks for PDF pages,
31
31
  cells for spreadsheet sheets.
32
32
 
33
33
  ```console
@@ -49,7 +49,7 @@ $ backdraft read t12-summary p1
49
49
  # t12-summary p1 (page 1 of 3)
50
50
 
51
51
  [bd:t12-summary:p1.c1:c2e8]
52
- Bridgeview Commons — Trailing Twelve Month Summary
52
+ Bridgeview Commons, Trailing Twelve Month Summary
53
53
 
54
54
  Property: Bridgeview Commons, 4400 Halsted Avenue, Columbus, OH 43214. 128 units across four three-story
55
55
  garden buildings on 6.2 acres, built in 1998 and substantially renovated between 2019 and 2021. This summary
@@ -69,7 +69,7 @@ Chunks fall on the document's own paragraphs. A PDF stores glyphs at
69
69
  coordinates, not paragraphs, so the extractor rebuilds the breaks from the line
70
70
  geometry before the chunker sees the page.
71
71
 
72
- Search results are citable too — no page read required to get an anchor:
72
+ Search results are citable too, no page read required to get an anchor:
73
73
 
74
74
  ```console
75
75
  $ backdraft search "24850000"
@@ -112,12 +112,12 @@ $ echo $?
112
112
  2
113
113
  ```
114
114
 
115
- Exit 2 means a citation did not resolve — the code a `Stop` hook or a CI job
115
+ Exit 2 means a citation did not resolve, the code a `Stop` hook or a CI job
116
116
  gates on.
117
117
 
118
118
  **Render.** One self-contained HTML file: the document, the receipts, the
119
- evidence — cited page images, spreadsheet cells in place, the machine-readable
120
- record — all embedded, no network, nothing to install for the reader.
119
+ evidence, cited page images, spreadsheet cells in place, the machine-readable
120
+ record, all embedded, no network, nothing to install for the reader.
121
121
 
122
122
  ```console
123
123
  $ backdraft render memo.md --to html
@@ -135,13 +135,13 @@ in at [`demo/memo.backdraft.html`](demo/memo.backdraft.html).
135
135
 
136
136
  Reading is a **gate**: source documents reach the writer only through `read` and
137
137
  `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 — and
138
+ **ledger**, so the set of citable things is exactly the set of things shown, and
139
139
  a citation to something the writer never saw is a distinguishable failure rather
140
140
  than an invisible one. **Anchors are content-addressed**: a token's identity
141
141
  derives from the document's content and a location inside it, and it binds to an
142
142
  extraction snapshot, so re-ingesting the same bytes yields the same tokens and a
143
143
  changed source yields `drifted` instead of a broken link. **The receipt travels
144
- with the claim** — an anchor is not a pointer but a pointer plus the verbatim
144
+ with the claim**, an anchor is not a pointer but a pointer plus the verbatim
145
145
  snippet and its hash, which is why the finished artifact is defensible with the
146
146
  registry deleted and the sources gone. **Verification is a set of switches,
147
147
  default off**: resolution is inherent to binding, but `value-trace`, `overlap`
@@ -158,31 +158,29 @@ artifact. Nothing is ever warned about and dropped.
158
158
  ## What lives where
159
159
 
160
160
  A working directory stays clean: the authored document and its artifact are
161
- the only visible outputs. Everything else — registry, credentials, bind
162
- records — lives under one hidden `.backdraft/` directory, and
161
+ the only visible outputs. Everything else, registry, credentials, bind
162
+ records, lives under one hidden `.backdraft/` directory, and
163
163
  `backdraft clean` tidies strays from older versions. The registry contains the
164
164
  full text of everything ingested, so **gitignore `.backdraft/` for
165
165
  confidential corpora**.
166
166
 
167
167
  ## Install
168
168
 
169
- Not yet on PyPI — install from the repository:
170
-
171
169
  ```bash
172
- uv tool install git+https://github.com/spencerbraun/backdraft # CLI
173
- uv add "backdraft @ git+https://github.com/spencerbraun/backdraft" # as a dependency
174
- pip install git+https://github.com/spencerbraun/backdraft # or plain pip
170
+ uv tool install "backdraft[vlm]" # CLI, recommended form
171
+ uv add backdraft # as a dependency
172
+ pip install backdraft # or plain pip
175
173
  ```
176
174
 
177
175
  Python 3.13+. Two extras:
178
176
 
179
177
  | Extra | Adds | For |
180
178
  |---|---|---|
181
- | `[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 |
179
+ | `[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
180
  | `[entail]` | `anthropic` | `bind --check entail`, the model-judge verifier |
183
181
 
184
182
  ```bash
185
- pip install "backdraft[vlm,entail] @ git+https://github.com/spencerbraun/backdraft"
183
+ pip install "backdraft[vlm,entail]"
186
184
  ```
187
185
 
188
186
  From a checkout:
@@ -196,7 +194,7 @@ uv run pytest
196
194
  ## Skills
197
195
 
198
196
  Three agent skills ship in [`skills/`](skills). Copy the ones you want into your
199
- agent's skills directory — for Claude Code, `~/.claude/skills/` (personal) or
197
+ agent's skills directory, for Claude Code, `~/.claude/skills/` (personal) or
200
198
  `.claude/skills/` (per project):
201
199
 
202
200
  ```bash
@@ -216,11 +214,11 @@ the skills is required to use backdraft by hand.
216
214
 
217
215
  | Document | Is |
218
216
  |---|---|
219
- | [DESIGN.md](DESIGN.md) | why it is shaped this way — principles, architecture, decision log |
220
- | [SPEC.md](SPEC.md) | the builders' contract — types, grammar, DDL, CLI surface, module boundaries |
217
+ | [DESIGN.md](DESIGN.md) | why it is shaped this way, principles, architecture, decision log |
218
+ | [SPEC.md](SPEC.md) | the builders' contract, types, grammar, DDL, CLI surface, module boundaries |
221
219
  | [spec/tokens.md](spec/tokens.md) | the citation token grammar, normatively |
222
220
  | [spec/chunking.md](spec/chunking.md) | the deterministic chunker |
223
- | [spec/artifact.md](spec/artifact.md) | the artifact format — sidecar payload, legend, HTML rules |
221
+ | [spec/artifact.md](spec/artifact.md) | the artifact format, sidecar payload, legend, HTML rules |
224
222
  | [demo/walkthrough.md](demo/walkthrough.md) | the whole thing end to end, with real output |
225
223
 
226
224
  The three files under `spec/` are the portable specification: another
@@ -229,8 +227,8 @@ implementation reads them and nothing else.
229
227
  ## Status
230
228
 
231
229
  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 — exhibits,
233
- Excel region maps, more formats, `bd:calc` derivations — is written down in
230
+ by prose specs and golden-file tests. What is deliberately queued, exhibits,
231
+ Excel region maps, more formats, `bd:calc` derivations, is written down in
234
232
  [ROADMAP.md](ROADMAP.md).
235
233
 
236
234
  MIT licensed.
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "backdraft"
3
- version = "0.2.0"
3
+ version = "0.2.1"
4
4
  description = "Drop-in provenance for factual claims: one click from any claim to the evidence behind it."
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.13"
@@ -36,6 +36,9 @@ build-backend = "hatchling.build"
36
36
  [tool.hatch.build.targets.wheel]
37
37
  packages = ["src/backdraft"]
38
38
 
39
+ [tool.hatch.build.targets.wheel.force-include]
40
+ "skills" = "backdraft/skills"
41
+
39
42
  [tool.pytest.ini_options]
40
43
  testpaths = ["tests"]
41
44
  addopts = "-q"
@@ -3,7 +3,7 @@
3
3
  <head>
4
4
  <meta charset="utf-8">
5
5
  <meta name="viewport" content="width=device-width, initial-scale=1">
6
- <title>Backdraft — documentation</title>
6
+ <title>Backdraft, documentation</title>
7
7
  <meta name="description" content="Install, concepts, the full workflow, CLI reference, verification, the artifact format, and agent setup for Backdraft.">
8
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
9
  <style>
@@ -106,25 +106,37 @@ footer{text-align:center;font-family:var(--sans);font-size:.74rem;color:var(--mu
106
106
 
107
107
  <main>
108
108
  <h1>Documentation</h1>
109
- <p class="lede">Everything to run the loop: install, the concepts that make it
110
- trustworthy, every command, and what ends up on disk. The normative format
111
- specs live <a href="https://github.com/spencerbraun/backdraft/tree/main/spec">in
112
- the repo</a>; agents should start from <a href="llms.txt">llms.txt</a>.</p>
109
+ <p class="lede">Backdraft is three things that ship together: a Python CLI
110
+ (<code>pip install backdraft</code>) that ingests documents and checks
111
+ citations, a one-page agent skill that makes your agent write through it, and
112
+ a self-contained file format for the output. There is no server and no SDK to
113
+ integrate, the CLI is the whole system. This page covers install, the
114
+ concepts, every command, and what ends up on disk. The normative format specs
115
+ live <a href="https://github.com/spencerbraun/backdraft/tree/main/spec">in the
116
+ repo</a>, and agents should start from <a href="llms.txt">llms.txt</a>.</p>
113
117
 
114
118
  <section id="install">
115
119
  <h2>Install</h2>
116
- <pre>uv tool install "backdraft[vlm] @ git+https://github.com/spencerbraun/backdraft"</pre>
120
+ <pre>uv tool install "backdraft[vlm]"</pre>
117
121
  <p>Python 3.13+. The <code>[vlm]</code> extra adds the vision-model PDF
118
- extractor — the recommended path for real PDFs (glossy layouts, info boxes,
119
- scans) — and needs <code>poppler</code> on your PATH
122
+ extractor, the recommended path for real PDFs (glossy layouts, info boxes,
123
+ scans), and needs <code>poppler</code> on your PATH
120
124
  (<code>brew install poppler</code>). Without it, PDFs fall back to the keyless
121
125
  embedded-text layer, and ingest says so.</p>
122
- <p>The VLM extractor runs only on explicit, backdraft-scoped consent: set
123
- <code>BACKDRAFT_VLM_API_KEY</code> in <code>.backdraft/env</code> (written by
124
- <code>backdraft init</code>) or the environment. The default provider is
125
- OpenRouter running Gemini Flash; ambient <code>OPENAI_API_KEY</code>-style
126
- variables are deliberately never read. <code>[entail]</code> adds the optional
127
- model-judge verifier.</p>
126
+ <h3>Bring a model provider</h3>
127
+ <p>The vision extractor needs a key you provide, and it runs only on explicit,
128
+ backdraft-scoped consent: set <code>BACKDRAFT_VLM_API_KEY</code> in
129
+ <code>.backdraft/env</code> (written by <code>backdraft init</code>) or the
130
+ environment. Ambient <code>OPENAI_API_KEY</code>-style variables are
131
+ deliberately never read.</p>
132
+ <p>Under the hood the client is the OpenAI SDK with an injectable
133
+ <code>base_url</code>, so any OpenAI-compatible provider works. The default is
134
+ OpenRouter (<code>https://openrouter.ai/api/v1</code>) running
135
+ <code>google/gemini-3.1-flash-lite-preview</code>, so the simplest setup is an
136
+ OpenRouter key. To point elsewhere, set <code>BACKDRAFT_VLM_BASE_URL</code>
137
+ and <code>BACKDRAFT_VLM_MODEL</code>, for example directly at OpenAI or at a
138
+ local server. <code>[entail]</code> adds the optional model-judge
139
+ verifier, keyed separately by <code>BACKDRAFT_ENTAIL_API_KEY</code>.</p>
128
140
  </section>
129
141
 
130
142
  <section id="concepts">
@@ -133,11 +145,11 @@ model-judge verifier.</p>
133
145
  <p>Source documents reach the writer only through <code>read</code>,
134
146
  <code>search</code> and <code>cell</code>, which stamp a <b>token</b> on every
135
147
  span they show and record the showing in a session <b>ledger</b>. The set of
136
- citable things is exactly the set of things shown — so a citation to something
148
+ citable things is exactly the set of things shown, so a citation to something
137
149
  the writer never saw is a distinguishable failure
138
150
  (<code>not_shown</code>), not an invisible one.</p>
139
151
  <h3>Tokens and receipts</h3>
140
- <p><code>bd:&lt;slug&gt;:&lt;locator&gt;:&lt;hash&gt;</code> — document,
152
+ <p><code>bd:&lt;slug&gt;:&lt;locator&gt;:&lt;hash&gt;</code>: document,
141
153
  place, and a content-hash of the exact text cited. Locators are media-native:
142
154
  <code>p8</code> (a page), <code>p8.c3</code> (a paragraph-scale chunk),
143
155
  <code>rent-roll!B10</code> (a cell). An anchor is not a pointer: it carries the
@@ -146,7 +158,7 @@ the registry deleted and the sources gone.</p>
146
158
  <h3>Drift</h3>
147
159
  <p>Re-ingesting identical bytes yields identical tokens. When a source
148
160
  changes, citations into it resolve against the superseded snapshot and report
149
- <code>drifted</code> — the artifact shows what was cited and what stands now,
161
+ <code>drifted</code>. The artifact shows what was cited and what stands now,
150
162
  as a word-diff.</p>
151
163
  </section>
152
164
 
@@ -196,7 +208,7 @@ skill</a> imposes the gate, and you receive the artifact.</div>
196
208
  <tr><th>Code</th><th>Means</th></tr>
197
209
  <tr><td><code>0</code></td><td>Every citation resolved.</td></tr>
198
210
  <tr><td><code>1</code></td><td>Usage or environment error.</td></tr>
199
- <tr><td><code>2</code></td><td>Bind completed and something did not resolve — the code a CI job or Stop-hook gates on. Verification verdicts never produce it.</td></tr>
211
+ <tr><td><code>2</code></td><td>Bind completed and something did not resolve, the code a CI job or Stop-hook gates on. Verification verdicts never produce it.</td></tr>
200
212
  </table>
201
213
  </section>
202
214
 
@@ -211,7 +223,7 @@ output; multiple citations are <code>;</code>-separated in one href:</p>
211
223
  evidence supports.</li>
212
224
  <li><b>Cite only what you were shown.</b> Never construct or edit a token by
213
225
  hand.</li>
214
- <li><b>Uncited prose is fine</b> for recommendations and framing — cite facts,
226
+ <li><b>Uncited prose is fine</b> for recommendations and framing, cite facts,
215
227
  not opinions.</li>
216
228
  <li><b>Never fix a failure by deleting its token.</b> A kept failure is the
217
229
  honest outcome; the artifact will show it plainly.</li>
@@ -222,7 +234,7 @@ artifact's subtitle.</li>
222
234
 
223
235
  <section id="verification">
224
236
  <h2>Verification</h2>
225
- <p>Independent switches, off by default, recorded as graded evidence — never
237
+ <p>Independent switches, off by default, recorded as graded evidence, never
226
238
  gates. <code>value-trace</code> finds every figure in a claim in its cited
227
239
  source, reading through thousands separators, currency, scale suffixes
228
240
  ($1.4M ≡ 1,400,000), percent-vs-decimal (7.7% ≡ 0.077), accounting negatives
@@ -238,11 +250,11 @@ Record layer in plain language.</p>
238
250
  <h2>Files &amp; the artifact</h2>
239
251
  <table>
240
252
  <tr><th>File</th><th>Is</th></tr>
241
- <tr><td><code>memo.md</code></td><td>What the writer wrote — prose plus tokens. Yours; re-bindable.</td></tr>
253
+ <tr><td><code>memo.md</code></td><td>What the writer wrote, prose plus tokens. Yours; re-bindable.</td></tr>
242
254
  <tr><td><code>memo.backdraft.html</code></td><td>The deliverable: document, receipts and evidence in one self-contained file. No network (CSP-enforced), nothing to install, degrades to readable footnotes if scripts are stripped.</td></tr>
243
- <tr><td><code>.backdraft/</code></td><td>The machinery: registry, credentials, and the bind record (<code>records/memo.backdraft.json</code> — the machine-readable run, also embedded in the artifact as a JSON island with a self-describing legend).</td></tr>
255
+ <tr><td><code>.backdraft/</code></td><td>The machinery: registry, credentials, and the bind record (<code>records/memo.backdraft.json</code>, the machine-readable run, also embedded in the artifact as a JSON island with a self-describing legend).</td></tr>
244
256
  </table>
245
- <p>The artifact embeds only <em>cited</em> evidence — a memo citing ten pages
257
+ <p>The artifact embeds only <em>cited</em> evidence, a memo citing ten pages
246
258
  of a gigabyte corpus is a ~2&nbsp;MB file. Recipients need the one
247
259
  <code>.html</code> file and nothing else.</p>
248
260
  </section>
@@ -266,18 +278,18 @@ token-efficient reference for any agent is <a href="llms.txt">llms.txt</a>
266
278
  <code>--config</code>. A generic <code>OPENAI_API_KEY</code> in your shell is
267
279
  not consent to send documents anywhere.</li>
268
280
  <li><b>Artifacts make no network requests</b>, enforced by a
269
- Content-Security-Policy the browser applies — no fonts, no analytics, no
281
+ Content-Security-Policy the browser applies, no fonts, no analytics, no
270
282
  phone-home.</li>
271
283
  <li><b>Everything is local.</b> The only network calls in the system are the
272
284
  optional VLM/entail model calls you explicitly key.</li>
273
- <li><b>Gitignore <code>.backdraft/</code></b> for confidential corpora — the
285
+ <li><b>Gitignore <code>.backdraft/</code></b> for confidential corpora, the
274
286
  registry contains the full text of everything ingested.</li>
275
287
  </ul>
276
288
  </section>
277
289
 
278
290
  <section id="faq">
279
291
  <h2>FAQ</h2>
280
- <h3>Ingest says it fell back to the text layer — why?</h3>
292
+ <h3>Ingest says it fell back to the text layer, why?</h3>
281
293
  <p>The note names the condition: either the <code>[vlm]</code> extra isn't
282
294
  installed, or <code>BACKDRAFT_VLM_API_KEY</code> isn't set. Fix the named one.
283
295
  Text-layer receipts are fine for clean digital PDFs; glossy or scanned ones
@@ -288,7 +300,7 @@ registries, run <code>backdraft snapshot-pages &lt;slug&gt;</code> (local,
288
300
  free), then re-bind.</p>
289
301
  <h3>What does <code>not_shown</code> actually catch?</h3>
290
302
  <p>A real token, valid in the registry, that the writing session was never
291
- shown — i.e. the writer cited something it didn't read. Only a session ledger
303
+ shown, i.e. the writer cited something it didn't read. Only a session ledger
292
304
  makes this class of failure visible; that is why the skill starts one.</p>
293
305
  <h3>Can a reader trust an artifact without installing backdraft?</h3>
294
306
  <p>That's the design goal: the receipts, the evidence, and a machine-readable
@@ -296,7 +308,7 @@ record with its own decoding legend are inside the file. The
296
308
  <a href="https://github.com/spencerbraun/backdraft/blob/main/spec/artifact.md">artifact
297
309
  spec</a> lists the checks a skeptic can run with nothing but the file.</p>
298
310
  <h3>Is this fact-checking?</h3>
299
- <p>No — provenance. Backdraft proves <i>where a claim came from</i> and shows
311
+ <p>No, provenance. Backdraft proves <i>where a claim came from</i> and shows
300
312
  you the source; the optional verifiers add deterministic evidence like "this
301
313
  figure appears in that cell." Judgment stays with the reader.</p>
302
314
  </section>