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.
Files changed (135) hide show
  1. backdraft-0.3.0/.github/workflows/ci.yml +14 -0
  2. backdraft-0.3.0/.github/workflows/release.yml +23 -0
  3. backdraft-0.3.0/.github/workflows/site.yml +26 -0
  4. {backdraft-0.2.0 → backdraft-0.3.0}/.gitignore +1 -0
  5. {backdraft-0.2.0 → backdraft-0.3.0}/DESIGN.md +19 -19
  6. {backdraft-0.2.0 → backdraft-0.3.0}/PKG-INFO +34 -31
  7. {backdraft-0.2.0 → backdraft-0.3.0}/README.md +29 -30
  8. {backdraft-0.2.0 → backdraft-0.3.0}/ROADMAP.md +20 -21
  9. {backdraft-0.2.0 → backdraft-0.3.0}/SPEC.md +4 -4
  10. {backdraft-0.2.0 → backdraft-0.3.0}/demo/generate_sources.py +31 -2
  11. {backdraft-0.2.0 → backdraft-0.3.0}/demo/memo.backdraft.html +396 -17
  12. {backdraft-0.2.0 → backdraft-0.3.0}/demo/sources/t12-summary.pdf +2 -2
  13. backdraft-0.3.0/demo/sources/underwriting-model.xlsx +0 -0
  14. {backdraft-0.2.0 → backdraft-0.3.0}/pyproject.toml +10 -1
  15. {backdraft-0.2.0 → backdraft-0.3.0}/site/demo.html +396 -17
  16. {backdraft-0.2.0 → backdraft-0.3.0}/site/docs.html +62 -37
  17. backdraft-0.3.0/site/index.html +459 -0
  18. {backdraft-0.2.0 → backdraft-0.3.0}/site/llms.txt +10 -7
  19. {backdraft-0.2.0 → backdraft-0.3.0}/spec/artifact.md +12 -4
  20. {backdraft-0.2.0 → backdraft-0.3.0}/src/backdraft/bind/evidence.py +44 -7
  21. {backdraft-0.2.0 → backdraft-0.3.0}/src/backdraft/cli.py +53 -0
  22. {backdraft-0.2.0 → backdraft-0.3.0}/src/backdraft/extract/base.py +20 -4
  23. backdraft-0.3.0/src/backdraft/extract/csv.py +97 -0
  24. backdraft-0.3.0/src/backdraft/extract/image.py +88 -0
  25. {backdraft-0.2.0 → backdraft-0.3.0}/src/backdraft/extract/vlm.py +3 -4
  26. {backdraft-0.2.0 → backdraft-0.3.0}/src/backdraft/extract/vlm_settings.py +11 -11
  27. {backdraft-0.2.0 → backdraft-0.3.0}/src/backdraft/extract/xlsx.py +154 -10
  28. {backdraft-0.2.0 → backdraft-0.3.0}/src/backdraft/gate/searcher.py +3 -3
  29. {backdraft-0.2.0 → backdraft-0.3.0}/src/backdraft/kernel/artifact.py +7 -2
  30. {backdraft-0.2.0 → backdraft-0.3.0}/src/backdraft/kernel/model.py +8 -2
  31. {backdraft-0.2.0 → backdraft-0.3.0}/src/backdraft/registry/ledger.py +1 -1
  32. {backdraft-0.2.0 → backdraft-0.3.0}/src/backdraft/registry/schema.sql +8 -1
  33. {backdraft-0.2.0 → backdraft-0.3.0}/src/backdraft/registry/store.py +37 -5
  34. {backdraft-0.2.0 → backdraft-0.3.0}/src/backdraft/render/html.py +134 -16
  35. {backdraft-0.2.0 → backdraft-0.3.0}/tests/golden/render/backfill.sidecar.json +1 -1
  36. {backdraft-0.2.0 → backdraft-0.3.0}/tests/golden/render/demo.sidecar.json +1 -1
  37. {backdraft-0.2.0 → backdraft-0.3.0}/tests/test_bind_evidence.py +35 -0
  38. {backdraft-0.2.0 → backdraft-0.3.0}/tests/test_cli.py +17 -0
  39. {backdraft-0.2.0 → backdraft-0.3.0}/tests/test_extract.py +163 -4
  40. {backdraft-0.2.0 → backdraft-0.3.0}/uv.lock +1 -1
  41. backdraft-0.2.0/demo/sources/underwriting-model.xlsx +0 -0
  42. backdraft-0.2.0/research/20260727-excel-region-detection.md +0 -53
  43. backdraft-0.2.0/research/20260727-research-findings.md +0 -182
  44. backdraft-0.2.0/site/index.html +0 -410
  45. {backdraft-0.2.0 → backdraft-0.3.0}/.python-version +0 -0
  46. {backdraft-0.2.0 → backdraft-0.3.0}/LICENSE +0 -0
  47. {backdraft-0.2.0 → backdraft-0.3.0}/assets/backdraft-mark.svg +0 -0
  48. {backdraft-0.2.0 → backdraft-0.3.0}/demo/memo.md +0 -0
  49. {backdraft-0.2.0 → backdraft-0.3.0}/demo/walkthrough.md +0 -0
  50. {backdraft-0.2.0 → backdraft-0.3.0}/skills/backdraft/SKILL.md +0 -0
  51. {backdraft-0.2.0 → backdraft-0.3.0}/skills/backdraft-artifact/SKILL.md +0 -0
  52. {backdraft-0.2.0 → backdraft-0.3.0}/skills/backdraft-backfill/SKILL.md +0 -0
  53. {backdraft-0.2.0 → backdraft-0.3.0}/spec/chunking.md +0 -0
  54. {backdraft-0.2.0 → backdraft-0.3.0}/spec/tokens.md +0 -0
  55. {backdraft-0.2.0 → backdraft-0.3.0}/src/backdraft/__init__.py +0 -0
  56. {backdraft-0.2.0 → backdraft-0.3.0}/src/backdraft/bind/__init__.py +0 -0
  57. {backdraft-0.2.0 → backdraft-0.3.0}/src/backdraft/bind/binder.py +0 -0
  58. {backdraft-0.2.0 → backdraft-0.3.0}/src/backdraft/bind/cli.py +0 -0
  59. {backdraft-0.2.0 → backdraft-0.3.0}/src/backdraft/bind/verify/__init__.py +0 -0
  60. {backdraft-0.2.0 → backdraft-0.3.0}/src/backdraft/bind/verify/base.py +0 -0
  61. {backdraft-0.2.0 → backdraft-0.3.0}/src/backdraft/bind/verify/entail.py +0 -0
  62. {backdraft-0.2.0 → backdraft-0.3.0}/src/backdraft/bind/verify/overlap.py +0 -0
  63. {backdraft-0.2.0 → backdraft-0.3.0}/src/backdraft/bind/verify/recompute.py +0 -0
  64. {backdraft-0.2.0 → backdraft-0.3.0}/src/backdraft/bind/verify/value_trace.py +0 -0
  65. {backdraft-0.2.0 → backdraft-0.3.0}/src/backdraft/cli_context.py +0 -0
  66. {backdraft-0.2.0 → backdraft-0.3.0}/src/backdraft/credentials.py +0 -0
  67. {backdraft-0.2.0 → backdraft-0.3.0}/src/backdraft/extract/__init__.py +0 -0
  68. {backdraft-0.2.0 → backdraft-0.3.0}/src/backdraft/extract/pdf_text.py +0 -0
  69. {backdraft-0.2.0 → backdraft-0.3.0}/src/backdraft/extract/text.py +0 -0
  70. {backdraft-0.2.0 → backdraft-0.3.0}/src/backdraft/gate/__init__.py +0 -0
  71. {backdraft-0.2.0 → backdraft-0.3.0}/src/backdraft/gate/cli.py +0 -0
  72. {backdraft-0.2.0 → backdraft-0.3.0}/src/backdraft/gate/reader.py +0 -0
  73. {backdraft-0.2.0 → backdraft-0.3.0}/src/backdraft/kernel/__init__.py +0 -0
  74. {backdraft-0.2.0 → backdraft-0.3.0}/src/backdraft/kernel/chunking.py +0 -0
  75. {backdraft-0.2.0 → backdraft-0.3.0}/src/backdraft/kernel/claims.py +0 -0
  76. {backdraft-0.2.0 → backdraft-0.3.0}/src/backdraft/kernel/errors.py +0 -0
  77. {backdraft-0.2.0 → backdraft-0.3.0}/src/backdraft/kernel/hashing.py +0 -0
  78. {backdraft-0.2.0 → backdraft-0.3.0}/src/backdraft/kernel/tokens.py +0 -0
  79. {backdraft-0.2.0 → backdraft-0.3.0}/src/backdraft/registry/__init__.py +0 -0
  80. {backdraft-0.2.0 → backdraft-0.3.0}/src/backdraft/render/__init__.py +0 -0
  81. {backdraft-0.2.0 → backdraft-0.3.0}/src/backdraft/render/_text.py +0 -0
  82. {backdraft-0.2.0 → backdraft-0.3.0}/src/backdraft/render/cli.py +0 -0
  83. {backdraft-0.2.0 → backdraft-0.3.0}/src/backdraft/render/footnotes.py +0 -0
  84. {backdraft-0.2.0 → backdraft-0.3.0}/src/backdraft/render/markdown.py +0 -0
  85. {backdraft-0.2.0 → backdraft-0.3.0}/src/backdraft/render/placement.py +0 -0
  86. {backdraft-0.2.0 → backdraft-0.3.0}/src/backdraft/render/sidecar.py +0 -0
  87. {backdraft-0.2.0 → backdraft-0.3.0}/tests/conftest.py +0 -0
  88. {backdraft-0.2.0 → backdraft-0.3.0}/tests/conftest_registry.py +0 -0
  89. {backdraft-0.2.0 → backdraft-0.3.0}/tests/conftest_render.py +0 -0
  90. {backdraft-0.2.0 → backdraft-0.3.0}/tests/fakes.py +0 -0
  91. {backdraft-0.2.0 → backdraft-0.3.0}/tests/gate/conftest.py +0 -0
  92. {backdraft-0.2.0 → backdraft-0.3.0}/tests/gate/fake_registry.py +0 -0
  93. {backdraft-0.2.0 → backdraft-0.3.0}/tests/gate/test_gate_cli.py +0 -0
  94. {backdraft-0.2.0 → backdraft-0.3.0}/tests/gate/test_gate_ledger.py +0 -0
  95. {backdraft-0.2.0 → backdraft-0.3.0}/tests/gate/test_gate_reader.py +0 -0
  96. {backdraft-0.2.0 → backdraft-0.3.0}/tests/gate/test_gate_searcher.py +0 -0
  97. {backdraft-0.2.0 → backdraft-0.3.0}/tests/gate/test_reader_cells.py +0 -0
  98. {backdraft-0.2.0 → backdraft-0.3.0}/tests/golden/bind/backfill.bound.md +0 -0
  99. {backdraft-0.2.0 → backdraft-0.3.0}/tests/golden/bind/frontwalk.bound.md +0 -0
  100. {backdraft-0.2.0 → backdraft-0.3.0}/tests/golden/chunking/long_tail.json +0 -0
  101. {backdraft-0.2.0 → backdraft-0.3.0}/tests/golden/chunking/long_tail.txt +0 -0
  102. {backdraft-0.2.0 → backdraft-0.3.0}/tests/golden/chunking/many_small.json +0 -0
  103. {backdraft-0.2.0 → backdraft-0.3.0}/tests/golden/chunking/many_small.txt +0 -0
  104. {backdraft-0.2.0 → backdraft-0.3.0}/tests/golden/chunking/mixed.json +0 -0
  105. {backdraft-0.2.0 → backdraft-0.3.0}/tests/golden/chunking/mixed.txt +0 -0
  106. {backdraft-0.2.0 → backdraft-0.3.0}/tests/golden/chunking/one_giant.json +0 -0
  107. {backdraft-0.2.0 → backdraft-0.3.0}/tests/golden/chunking/one_giant.txt +0 -0
  108. {backdraft-0.2.0 → backdraft-0.3.0}/tests/golden/chunking/short_page.json +0 -0
  109. {backdraft-0.2.0 → backdraft-0.3.0}/tests/golden/chunking/short_page.txt +0 -0
  110. {backdraft-0.2.0 → backdraft-0.3.0}/tests/golden/chunking/unsplittable.json +0 -0
  111. {backdraft-0.2.0 → backdraft-0.3.0}/tests/golden/chunking/unsplittable.txt +0 -0
  112. {backdraft-0.2.0 → backdraft-0.3.0}/tests/golden/render/backfill.footnotes.md +0 -0
  113. {backdraft-0.2.0 → backdraft-0.3.0}/tests/golden/render/demo.footnotes.md +0 -0
  114. {backdraft-0.2.0 → backdraft-0.3.0}/tests/golden_util.py +0 -0
  115. {backdraft-0.2.0 → backdraft-0.3.0}/tests/test_bind_binder.py +0 -0
  116. {backdraft-0.2.0 → backdraft-0.3.0}/tests/test_bind_cli.py +0 -0
  117. {backdraft-0.2.0 → backdraft-0.3.0}/tests/test_bind_verify.py +0 -0
  118. {backdraft-0.2.0 → backdraft-0.3.0}/tests/test_chunking.py +0 -0
  119. {backdraft-0.2.0 → backdraft-0.3.0}/tests/test_claims.py +0 -0
  120. {backdraft-0.2.0 → backdraft-0.3.0}/tests/test_credentials.py +0 -0
  121. {backdraft-0.2.0 → backdraft-0.3.0}/tests/test_footnotes.py +0 -0
  122. {backdraft-0.2.0 → backdraft-0.3.0}/tests/test_gate_integration.py +0 -0
  123. {backdraft-0.2.0 → backdraft-0.3.0}/tests/test_hashing.py +0 -0
  124. {backdraft-0.2.0 → backdraft-0.3.0}/tests/test_html.py +0 -0
  125. {backdraft-0.2.0 → backdraft-0.3.0}/tests/test_invariants.py +0 -0
  126. {backdraft-0.2.0 → backdraft-0.3.0}/tests/test_model.py +0 -0
  127. {backdraft-0.2.0 → backdraft-0.3.0}/tests/test_placement.py +0 -0
  128. {backdraft-0.2.0 → backdraft-0.3.0}/tests/test_records_location.py +0 -0
  129. {backdraft-0.2.0 → backdraft-0.3.0}/tests/test_registry.py +0 -0
  130. {backdraft-0.2.0 → backdraft-0.3.0}/tests/test_reingest.py +0 -0
  131. {backdraft-0.2.0 → backdraft-0.3.0}/tests/test_render_cli.py +0 -0
  132. {backdraft-0.2.0 → backdraft-0.3.0}/tests/test_render_markdown.py +0 -0
  133. {backdraft-0.2.0 → backdraft-0.3.0}/tests/test_sidecar.py +0 -0
  134. {backdraft-0.2.0 → backdraft-0.3.0}/tests/test_tokens.py +0 -0
  135. {backdraft-0.2.0 → backdraft-0.3.0}/tests/test_value_trace.py +0 -0
@@ -0,0 +1,14 @@
1
+ name: ci
2
+ on:
3
+ push:
4
+ branches: [main]
5
+ pull_request:
6
+ jobs:
7
+ test:
8
+ runs-on: ubuntu-latest
9
+ steps:
10
+ - uses: actions/checkout@v4
11
+ - uses: astral-sh/setup-uv@v5
12
+ - run: uv sync --all-extras
13
+ - run: uv run pytest
14
+ - run: uv build
@@ -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
@@ -22,3 +22,4 @@ uv.lock.bak
22
22
 
23
23
  # Claude Code session state and agent worktrees
24
24
  .claude/
25
+ .private/
@@ -1,12 +1,12 @@
1
1
  # Backdraft — Design
2
2
 
3
- Status: v0 design, 2026-07-27. Decisions below are made unless marked open. Research backing this is in [research/20260727-research-findings.md](research/20260727-research-findings.md).
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. Extracted from what was built inside the HighRoad app, redesigned from scratch, released standalone. 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.
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
- Not a port. The app proved the product (citations worked great from the frontend) but its mechanism was path-dependent: random UUIDs, four citation grammars, four resolver copies, silent failure everywhere, anchors that die on reprocessing. We port the ideas, not the code.
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.** The app's only working sub-page attribution was Excel, because `[B10]` markers sat in the text the model read. Human-readable location in the token; side-channel geometry rots (bbox was plumbed through six layers and read by nothing).
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 (fixing pipeline-v2's gap where search returned nothing citable and forced a page read).
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 app-developer drop-in (LoanCore validation-layer shape).
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 the app couldn't express.
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 — the column the app never had)
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
- Ported ideas from pipeline-v2's read tool, rebuilt local:
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 made Excel the app's only working sub-page attribution. Hardening kept: dimension caps, inflated-sheet placeholders, trimming.
72
- - Fixes over the app: search mints citable snippet tokens; Excel long-context gets structural navigation (detected regions/ranges) instead of char-offset slicing (open design area).
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 (the Clarion use).
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 (what the app did).
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 Darnell trial: presence of a generic key in the environment is not consent to spend it or send documents to its provider. |
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 ported from embed-ses `generate-preview`: WebP quality 85 for pages; sheet windows as compact JSON with typed cell values and column-letter headers. |
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 Darnell trial lives here)
143
- - Distribution: PyPI (name `backdraft` verified free 2026-07-28), skill
144
- registration; domain backdraft.dev acquired 2026-07-28
145
- - First external user (LoanCore validation layer vs Clarion eval substrate vs
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.2.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 — the memo with the actual cited pages
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) — one page for people, and
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 git+https://github.com/spencerbraun/backdraft
47
+ uv tool install "backdraft[vlm]"
44
48
  cd demo
45
49
  ```
46
50
 
47
- **Ingest** the sources. Every anchor is minted here — chunks for PDF pages,
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 — Trailing Twelve Month Summary
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 — no page read required to get an anchor:
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 — the code a `Stop` hook or a CI job
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 — cited page images, spreadsheet cells in place, the machine-readable
137
- record — all embedded, no network, nothing to install for the reader.
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 — and
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** — an anchor is not a pointer but a pointer plus the verbatim
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 — registry, credentials, bind
179
- records — lives under one hidden `.backdraft/` directory, and
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 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
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 — 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 |
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] @ git+https://github.com/spencerbraun/backdraft"
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 in [`skills/`](skills). Copy the ones you want into your
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
- cp -r skills/backdraft skills/backdraft-backfill skills/backdraft-artifact ~/.claude/skills/
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 — principles, architecture, decision log |
237
- | [SPEC.md](SPEC.md) | the builders' contract — types, grammar, DDL, CLI surface, module boundaries |
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 — sidecar payload, legend, HTML rules |
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 — exhibits,
250
- Excel region maps, more formats, `bd:calc` derivations — is written down in
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 — 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,
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 — Trailing Twelve Month Summary
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 — no page read required to get an anchor:
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 — the code a `Stop` hook or a CI job
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 — cited page images, spreadsheet cells in place, the machine-readable
120
- record — all embedded, no network, nothing to install for the reader.
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 — and
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** — an anchor is not a pointer but a pointer plus the verbatim
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 — registry, credentials, bind
162
- records — lives under one hidden `.backdraft/` directory, and
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 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
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 — 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 |
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] @ git+https://github.com/spencerbraun/backdraft"
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 in [`skills/`](skills). Copy the ones you want into your
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
- cp -r skills/backdraft skills/backdraft-backfill skills/backdraft-artifact ~/.claude/skills/
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 — principles, architecture, decision log |
220
- | [SPEC.md](SPEC.md) | the builders' contract — types, grammar, DDL, CLI surface, module boundaries |
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 — sidecar payload, legend, HTML rules |
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 — exhibits,
233
- Excel region maps, more formats, `bd:calc` derivations — is written down in
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 (`research/20260727-excel-region-detection.md`): a
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, plain text and Markdown.
27
- Each new format is one Extractor implementing the existing protocol — pages
28
- out, receipts quotable, nothing downstream changes.
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 renders
41
- slides to images through the VLM like PDF pages.
42
- - **XLS / XLSM** — legacy and macro workbooks. openpyxl covers xlsm;
43
- xls wants the calamine fallback embed-ses already proved in production.
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
- - **Sheet styling fidelity** — carry bold / fills / merges / column widths
56
- through ingest so sheet evidence looks like the workbook, not just its
57
- values. The shapes to port are embed-ses `generate-preview`'s
58
- (`SheetData.cell_styles`, `merged_ranges`, `column_widths`, `frozen_panes`).
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** — PyPI publish decision and the "backdraft" name check;
68
- install is git-only until then.
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