backdraft 0.12.0__tar.gz → 0.13.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {backdraft-0.12.0 → backdraft-0.13.0}/.claude-plugin/plugin.json +1 -1
- {backdraft-0.12.0 → backdraft-0.13.0}/DESIGN.md +3 -0
- {backdraft-0.12.0 → backdraft-0.13.0}/PKG-INFO +41 -5
- {backdraft-0.12.0 → backdraft-0.13.0}/README.md +40 -4
- {backdraft-0.12.0 → backdraft-0.13.0}/ROADMAP.md +234 -168
- {backdraft-0.12.0 → backdraft-0.13.0}/SPEC.md +6 -2
- {backdraft-0.12.0 → backdraft-0.13.0}/demo/memo.backdraft.html +2 -2
- {backdraft-0.12.0 → backdraft-0.13.0}/demo/walkthrough.md +2 -2
- {backdraft-0.12.0 → backdraft-0.13.0}/pyproject.toml +1 -1
- {backdraft-0.12.0 → backdraft-0.13.0}/site/demo.html +2 -2
- {backdraft-0.12.0 → backdraft-0.13.0}/site/docs.html +14 -4
- {backdraft-0.12.0 → backdraft-0.13.0}/site/llms.txt +22 -5
- {backdraft-0.12.0 → backdraft-0.13.0}/skills/backdraft/SKILL.md +25 -5
- {backdraft-0.12.0 → backdraft-0.13.0}/skills/backdraft-artifact/SKILL.md +1 -1
- {backdraft-0.12.0 → backdraft-0.13.0}/spec/artifact.md +6 -3
- {backdraft-0.12.0 → backdraft-0.13.0}/src/backdraft/bind/cli.py +22 -4
- {backdraft-0.12.0 → backdraft-0.13.0}/src/backdraft/bind/verify/entail.py +27 -10
- {backdraft-0.12.0 → backdraft-0.13.0}/src/backdraft/cli.py +263 -36
- {backdraft-0.12.0 → backdraft-0.13.0}/src/backdraft/cli_context.py +4 -2
- {backdraft-0.12.0 → backdraft-0.13.0}/src/backdraft/extract/__init__.py +2 -0
- {backdraft-0.12.0 → backdraft-0.13.0}/src/backdraft/extract/base.py +23 -4
- {backdraft-0.12.0 → backdraft-0.13.0}/src/backdraft/extract/snapshots.py +43 -4
- {backdraft-0.12.0 → backdraft-0.13.0}/src/backdraft/kernel/artifact.py +30 -1
- {backdraft-0.12.0 → backdraft-0.13.0}/src/backdraft/registry/store.py +20 -5
- {backdraft-0.12.0 → backdraft-0.13.0}/src/backdraft/render/cli.py +17 -2
- {backdraft-0.12.0 → backdraft-0.13.0}/src/backdraft/render/math.py +5 -1
- {backdraft-0.12.0 → backdraft-0.13.0}/tests/golden/render/backfill.sidecar.json +1 -1
- {backdraft-0.12.0 → backdraft-0.13.0}/tests/golden/render/demo.sidecar.json +1 -1
- {backdraft-0.12.0 → backdraft-0.13.0}/tests/test_bind_cli.py +89 -1
- {backdraft-0.12.0 → backdraft-0.13.0}/tests/test_cli.py +81 -0
- backdraft-0.13.0/tests/test_doctor.py +321 -0
- {backdraft-0.12.0 → backdraft-0.13.0}/tests/test_extract.py +1 -1
- {backdraft-0.12.0 → backdraft-0.13.0}/tests/test_forget.py +87 -1
- {backdraft-0.12.0 → backdraft-0.13.0}/tests/test_ingest_url.py +14 -0
- {backdraft-0.12.0 → backdraft-0.13.0}/tests/test_verify_cli.py +49 -1
- {backdraft-0.12.0 → backdraft-0.13.0}/uv.lock +1 -1
- {backdraft-0.12.0 → backdraft-0.13.0}/.claude-plugin/marketplace.json +0 -0
- {backdraft-0.12.0 → backdraft-0.13.0}/.github/workflows/ci.yml +0 -0
- {backdraft-0.12.0 → backdraft-0.13.0}/.github/workflows/release.yml +0 -0
- {backdraft-0.12.0 → backdraft-0.13.0}/.github/workflows/site.yml +0 -0
- {backdraft-0.12.0 → backdraft-0.13.0}/.gitignore +0 -0
- {backdraft-0.12.0 → backdraft-0.13.0}/.python-version +0 -0
- {backdraft-0.12.0 → backdraft-0.13.0}/LICENSE +0 -0
- {backdraft-0.12.0 → backdraft-0.13.0}/assets/backdraft-mark.svg +0 -0
- {backdraft-0.12.0 → backdraft-0.13.0}/demo/generate_sources.py +0 -0
- {backdraft-0.12.0 → backdraft-0.13.0}/demo/memo.md +0 -0
- {backdraft-0.12.0 → backdraft-0.13.0}/demo/sources/t12-summary.pdf +0 -0
- {backdraft-0.12.0 → backdraft-0.13.0}/demo/sources/underwriting-model.xlsx +0 -0
- {backdraft-0.12.0 → backdraft-0.13.0}/site/index.html +0 -0
- {backdraft-0.12.0 → backdraft-0.13.0}/site/privacy.html +0 -0
- {backdraft-0.12.0 → backdraft-0.13.0}/skills/backdraft-backfill/SKILL.md +0 -0
- {backdraft-0.12.0 → backdraft-0.13.0}/spec/chunking.md +0 -0
- {backdraft-0.12.0 → backdraft-0.13.0}/spec/registry.md +0 -0
- {backdraft-0.12.0 → backdraft-0.13.0}/spec/tokens.md +0 -0
- {backdraft-0.12.0 → backdraft-0.13.0}/src/backdraft/__init__.py +0 -0
- {backdraft-0.12.0 → backdraft-0.13.0}/src/backdraft/__main__.py +0 -0
- {backdraft-0.12.0 → backdraft-0.13.0}/src/backdraft/bind/__init__.py +0 -0
- {backdraft-0.12.0 → backdraft-0.13.0}/src/backdraft/bind/binder.py +0 -0
- {backdraft-0.12.0 → backdraft-0.13.0}/src/backdraft/bind/evidence.py +0 -0
- {backdraft-0.12.0 → backdraft-0.13.0}/src/backdraft/bind/verify/__init__.py +0 -0
- {backdraft-0.12.0 → backdraft-0.13.0}/src/backdraft/bind/verify/base.py +0 -0
- {backdraft-0.12.0 → backdraft-0.13.0}/src/backdraft/bind/verify/overlap.py +0 -0
- {backdraft-0.12.0 → backdraft-0.13.0}/src/backdraft/bind/verify/recompute.py +0 -0
- {backdraft-0.12.0 → backdraft-0.13.0}/src/backdraft/bind/verify/value_trace.py +0 -0
- {backdraft-0.12.0 → backdraft-0.13.0}/src/backdraft/credentials.py +0 -0
- {backdraft-0.12.0 → backdraft-0.13.0}/src/backdraft/extract/csv.py +0 -0
- {backdraft-0.12.0 → backdraft-0.13.0}/src/backdraft/extract/docx.py +0 -0
- {backdraft-0.12.0 → backdraft-0.13.0}/src/backdraft/extract/html.py +0 -0
- {backdraft-0.12.0 → backdraft-0.13.0}/src/backdraft/extract/image.py +0 -0
- {backdraft-0.12.0 → backdraft-0.13.0}/src/backdraft/extract/pdf_text.py +0 -0
- {backdraft-0.12.0 → backdraft-0.13.0}/src/backdraft/extract/pptx.py +0 -0
- {backdraft-0.12.0 → backdraft-0.13.0}/src/backdraft/extract/sheet.py +0 -0
- {backdraft-0.12.0 → backdraft-0.13.0}/src/backdraft/extract/text.py +0 -0
- {backdraft-0.12.0 → backdraft-0.13.0}/src/backdraft/extract/vlm.py +0 -0
- {backdraft-0.12.0 → backdraft-0.13.0}/src/backdraft/extract/vlm_settings.py +0 -0
- {backdraft-0.12.0 → backdraft-0.13.0}/src/backdraft/extract/xls.py +0 -0
- {backdraft-0.12.0 → backdraft-0.13.0}/src/backdraft/extract/xlsx.py +0 -0
- {backdraft-0.12.0 → backdraft-0.13.0}/src/backdraft/fetch.py +0 -0
- {backdraft-0.12.0 → backdraft-0.13.0}/src/backdraft/gate/__init__.py +0 -0
- {backdraft-0.12.0 → backdraft-0.13.0}/src/backdraft/gate/cli.py +0 -0
- {backdraft-0.12.0 → backdraft-0.13.0}/src/backdraft/gate/reader.py +0 -0
- {backdraft-0.12.0 → backdraft-0.13.0}/src/backdraft/gate/searcher.py +0 -0
- {backdraft-0.12.0 → backdraft-0.13.0}/src/backdraft/kernel/__init__.py +0 -0
- {backdraft-0.12.0 → backdraft-0.13.0}/src/backdraft/kernel/chunking.py +0 -0
- {backdraft-0.12.0 → backdraft-0.13.0}/src/backdraft/kernel/claims.py +0 -0
- {backdraft-0.12.0 → backdraft-0.13.0}/src/backdraft/kernel/errors.py +0 -0
- {backdraft-0.12.0 → backdraft-0.13.0}/src/backdraft/kernel/hashing.py +0 -0
- {backdraft-0.12.0 → backdraft-0.13.0}/src/backdraft/kernel/model.py +0 -0
- {backdraft-0.12.0 → backdraft-0.13.0}/src/backdraft/kernel/tokens.py +0 -0
- {backdraft-0.12.0 → backdraft-0.13.0}/src/backdraft/registry/__init__.py +0 -0
- {backdraft-0.12.0 → backdraft-0.13.0}/src/backdraft/registry/ledger.py +0 -0
- {backdraft-0.12.0 → backdraft-0.13.0}/src/backdraft/registry/schema.sql +0 -0
- {backdraft-0.12.0 → backdraft-0.13.0}/src/backdraft/render/__init__.py +0 -0
- {backdraft-0.12.0 → backdraft-0.13.0}/src/backdraft/render/_text.py +0 -0
- {backdraft-0.12.0 → backdraft-0.13.0}/src/backdraft/render/footnotes.py +0 -0
- {backdraft-0.12.0 → backdraft-0.13.0}/src/backdraft/render/html/__init__.py +0 -0
- {backdraft-0.12.0 → backdraft-0.13.0}/src/backdraft/render/html/assets.py +0 -0
- {backdraft-0.12.0 → backdraft-0.13.0}/src/backdraft/render/html/components.py +0 -0
- {backdraft-0.12.0 → backdraft-0.13.0}/src/backdraft/render/html/fmt.py +0 -0
- {backdraft-0.12.0 → backdraft-0.13.0}/src/backdraft/render/html/fmt_vectors.py +0 -0
- {backdraft-0.12.0 → backdraft-0.13.0}/src/backdraft/render/html/page.py +0 -0
- {backdraft-0.12.0 → backdraft-0.13.0}/src/backdraft/render/html/text.py +0 -0
- {backdraft-0.12.0 → backdraft-0.13.0}/src/backdraft/render/markdown.py +0 -0
- {backdraft-0.12.0 → backdraft-0.13.0}/src/backdraft/render/placement.py +0 -0
- {backdraft-0.12.0 → backdraft-0.13.0}/src/backdraft/render/sidecar.py +0 -0
- {backdraft-0.12.0 → backdraft-0.13.0}/src/backdraft/render/theme.py +0 -0
- {backdraft-0.12.0 → backdraft-0.13.0}/src/backdraft/render/themes/default.toml +0 -0
- {backdraft-0.12.0 → backdraft-0.13.0}/src/backdraft/render/themes/press.toml +0 -0
- {backdraft-0.12.0 → backdraft-0.13.0}/src/backdraft/render/themes/slate.toml +0 -0
- {backdraft-0.12.0 → backdraft-0.13.0}/tests/conftest.py +0 -0
- {backdraft-0.12.0 → backdraft-0.13.0}/tests/conftest_registry.py +0 -0
- {backdraft-0.12.0 → backdraft-0.13.0}/tests/conftest_render.py +0 -0
- {backdraft-0.12.0 → backdraft-0.13.0}/tests/continuation_util.py +0 -0
- {backdraft-0.12.0 → backdraft-0.13.0}/tests/css_util.py +0 -0
- {backdraft-0.12.0 → backdraft-0.13.0}/tests/fakes.py +0 -0
- {backdraft-0.12.0 → backdraft-0.13.0}/tests/gate/conftest.py +0 -0
- {backdraft-0.12.0 → backdraft-0.13.0}/tests/gate/fake_registry.py +0 -0
- {backdraft-0.12.0 → backdraft-0.13.0}/tests/gate/test_gate_adjoining.py +0 -0
- {backdraft-0.12.0 → backdraft-0.13.0}/tests/gate/test_gate_cli.py +0 -0
- {backdraft-0.12.0 → backdraft-0.13.0}/tests/gate/test_gate_ledger.py +0 -0
- {backdraft-0.12.0 → backdraft-0.13.0}/tests/gate/test_gate_reader.py +0 -0
- {backdraft-0.12.0 → backdraft-0.13.0}/tests/gate/test_gate_searcher.py +0 -0
- {backdraft-0.12.0 → backdraft-0.13.0}/tests/gate/test_reader_cells.py +0 -0
- {backdraft-0.12.0 → backdraft-0.13.0}/tests/gate/test_reader_show.py +0 -0
- {backdraft-0.12.0 → backdraft-0.13.0}/tests/golden/bind/backfill.bound.md +0 -0
- {backdraft-0.12.0 → backdraft-0.13.0}/tests/golden/bind/frontwalk.bound.md +0 -0
- {backdraft-0.12.0 → backdraft-0.13.0}/tests/golden/chunking/long_tail.json +0 -0
- {backdraft-0.12.0 → backdraft-0.13.0}/tests/golden/chunking/long_tail.txt +0 -0
- {backdraft-0.12.0 → backdraft-0.13.0}/tests/golden/chunking/many_small.json +0 -0
- {backdraft-0.12.0 → backdraft-0.13.0}/tests/golden/chunking/many_small.txt +0 -0
- {backdraft-0.12.0 → backdraft-0.13.0}/tests/golden/chunking/mixed.json +0 -0
- {backdraft-0.12.0 → backdraft-0.13.0}/tests/golden/chunking/mixed.txt +0 -0
- {backdraft-0.12.0 → backdraft-0.13.0}/tests/golden/chunking/one_giant.json +0 -0
- {backdraft-0.12.0 → backdraft-0.13.0}/tests/golden/chunking/one_giant.txt +0 -0
- {backdraft-0.12.0 → backdraft-0.13.0}/tests/golden/chunking/short_page.json +0 -0
- {backdraft-0.12.0 → backdraft-0.13.0}/tests/golden/chunking/short_page.txt +0 -0
- {backdraft-0.12.0 → backdraft-0.13.0}/tests/golden/chunking/unsplittable.json +0 -0
- {backdraft-0.12.0 → backdraft-0.13.0}/tests/golden/chunking/unsplittable.txt +0 -0
- {backdraft-0.12.0 → backdraft-0.13.0}/tests/golden/render/backfill.footnotes.md +0 -0
- {backdraft-0.12.0 → backdraft-0.13.0}/tests/golden/render/demo.footnotes.md +0 -0
- {backdraft-0.12.0 → backdraft-0.13.0}/tests/golden_util.py +0 -0
- {backdraft-0.12.0 → backdraft-0.13.0}/tests/test_bind_binder.py +0 -0
- {backdraft-0.12.0 → backdraft-0.13.0}/tests/test_bind_evidence.py +0 -0
- {backdraft-0.12.0 → backdraft-0.13.0}/tests/test_bind_verify.py +0 -0
- {backdraft-0.12.0 → backdraft-0.13.0}/tests/test_card_sizing.py +0 -0
- {backdraft-0.12.0 → backdraft-0.13.0}/tests/test_chunking.py +0 -0
- {backdraft-0.12.0 → backdraft-0.13.0}/tests/test_claims.py +0 -0
- {backdraft-0.12.0 → backdraft-0.13.0}/tests/test_credentials.py +0 -0
- {backdraft-0.12.0 → backdraft-0.13.0}/tests/test_docs.py +0 -0
- {backdraft-0.12.0 → backdraft-0.13.0}/tests/test_dry_run.py +0 -0
- {backdraft-0.12.0 → backdraft-0.13.0}/tests/test_extract_html.py +0 -0
- {backdraft-0.12.0 → backdraft-0.13.0}/tests/test_fetch.py +0 -0
- {backdraft-0.12.0 → backdraft-0.13.0}/tests/test_fmt_parity.py +0 -0
- {backdraft-0.12.0 → backdraft-0.13.0}/tests/test_footnotes.py +0 -0
- {backdraft-0.12.0 → backdraft-0.13.0}/tests/test_gate_integration.py +0 -0
- {backdraft-0.12.0 → backdraft-0.13.0}/tests/test_hashing.py +0 -0
- {backdraft-0.12.0 → backdraft-0.13.0}/tests/test_html.py +0 -0
- {backdraft-0.12.0 → backdraft-0.13.0}/tests/test_invariants.py +0 -0
- {backdraft-0.12.0 → backdraft-0.13.0}/tests/test_locate.py +0 -0
- {backdraft-0.12.0 → backdraft-0.13.0}/tests/test_model.py +0 -0
- {backdraft-0.12.0 → backdraft-0.13.0}/tests/test_placement.py +0 -0
- {backdraft-0.12.0 → backdraft-0.13.0}/tests/test_records_location.py +0 -0
- {backdraft-0.12.0 → backdraft-0.13.0}/tests/test_registry.py +0 -0
- {backdraft-0.12.0 → backdraft-0.13.0}/tests/test_reingest.py +0 -0
- {backdraft-0.12.0 → backdraft-0.13.0}/tests/test_render_cli.py +0 -0
- {backdraft-0.12.0 → backdraft-0.13.0}/tests/test_render_markdown.py +0 -0
- {backdraft-0.12.0 → backdraft-0.13.0}/tests/test_render_math.py +0 -0
- {backdraft-0.12.0 → backdraft-0.13.0}/tests/test_sidecar.py +0 -0
- {backdraft-0.12.0 → backdraft-0.13.0}/tests/test_snapshot_settings.py +0 -0
- {backdraft-0.12.0 → backdraft-0.13.0}/tests/test_snapshots.py +0 -0
- {backdraft-0.12.0 → backdraft-0.13.0}/tests/test_spec_registry.py +0 -0
- {backdraft-0.12.0 → backdraft-0.13.0}/tests/test_theme.py +0 -0
- {backdraft-0.12.0 → backdraft-0.13.0}/tests/test_tokens.py +0 -0
- {backdraft-0.12.0 → backdraft-0.13.0}/tests/test_value_trace.py +0 -0
- {backdraft-0.12.0 → backdraft-0.13.0}/tests/test_version.py +0 -0
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "backdraft",
|
|
3
3
|
"displayName": "Backdraft",
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.13.0",
|
|
5
5
|
"description": "Drop-in provenance for factual claims: one click from any claim to the evidence behind it. Ships the backdraft writing, backfill, and artifact-reading skills; the CLI installs separately (uv tool install backdraft).",
|
|
6
6
|
"author": {
|
|
7
7
|
"name": "Spencer Braun",
|
|
@@ -163,6 +163,9 @@ Self-contained HTML (plus the machine-readable sidecar): **quotes + receipts + e
|
|
|
163
163
|
| 2026-09-15 | **`verify --against <project>` checks a file you were sent against a registry you have, and discovery still infers nothing.** The 2026-08-24 row put `verify`'s source tier behind discovery from cwd, because an artifact is a file people forward and the folder it landed in says nothing about which registry produced it. That rule is kept exactly, and it had left no way to say what the recipient often does know — this file came out of *that* project — so the only route to a source check was to copy the artifact into the project first: a filesystem move performed to change a discovery result, repeated per artifact by an agent auditing several, and impossible for a reviewer without write access to the checkout. The flag answers the objection rather than overriding it. Discovery stays refusal-by-default and never looks at the artifact's location; `--against` is the caller asserting a link the tool must not infer, the same shape as `--slug` overruling a derived name — the tool derives nothing it is not told, and what it is told it takes as said. Three consequences follow from treating it as an assertion rather than a hint. It bypasses discovery instead of joining it, so it outranks `BACKDRAFT_HOME` as well as the walk: a name typed on one command is more specific than one exported for a whole shell, and a test pins flag, then environment, then walk. A path holding no registry is a usage error (exit 1) naming what was expected, never a fall back to tier one — a caller who named a registry and got the file-only check would read `sources: no .backdraft/ found from here` as a fact about the file rather than a typo — and it is refused *before* anything opens, because `Registry.open` creates what it does not find and a read-only audit must not leave a registry in whatever directory was typed. And the report names the registry that answered either way, on the `sources:` line and in `--json`'s `sources.registry`, resolved to an absolute root so the line from an inbox is the line from inside the project, which is what makes a wrong `--against` visible rather than just a run of `unresolved`. Both forms `BACKDRAFT_HOME` accepts — the project root or the `.backdraft` directory — are accepted through one rule (`cli_context.named_root` shares `find_root`'s), so the two ways of naming a registry cannot drift. Without the flag nothing moved: seven scenarios, in and out of the demo project, with and without `--json` and `BACKDRAFT_HOME`, diff byte-identical against the previous CLI, including the closing `[Re-check against the sources: …]` hint, which still names only the in-project route; pointing that hint at the flag is a wording change the acceptance ruled out of this item. |
|
|
164
164
|
| 2026-09-16 | **A chunk whose text may run on names the chunk it runs on into, decided by the chunks' own edges and never by the prose — so every page break counts.** The writing skill told an agent that "a claim that spans two chunks needs both tokens, not the nearest one", which is right and gave it nothing to act on: the gate handed back chunks, and whether the sentence being cited ended inside one was for the agent to notice by eye, mid-draft. When it did not, nothing complained — one token resolved, the receipt was real, the artifact rendered clean, and the half of the claim in the next chunk was uncited while looking cited, the one failure worse than `unresolved`, because `unresolved` says so. `gate.reader.adjoining` now answers it off the registry's own rows. On one page, two chunks adjoin when the whitespace between their stored offsets holds no blank line, tested with the chunker's own separator — `kernel.chunking.PARAGRAPH_BREAK`, made public so the question is asked of one pattern rather than a copy — because the chunker splits on blank lines before anything else, so two chunks with none between them are a paragraph rule 3 cut for length. A search hit carries that neighbour indented under its excerpt, labelled `same paragraph, before` or `after`, over the edge that touches the hit: the tail of the chunk before, the head of the chunk after. The page break was the decision, and it went the uncomfortable way on purpose. Each page is chunked alone and `pdf-text` writes no whitespace at a page's edges, so nothing a chunk carries tells a paragraph that crosses the break from one that ended at it; the only way to tell would be a sentence test at the join, and the obvious one — rule 3's own terminal-punctuation definition — was rejected twice over. It would decide what gets minted, which a guess must not. And its miss is exactly this row's failure: a paragraph that crosses the break at a full stop, under a claim spanning those two sentences. So a page break always adjoins, labelled `previous page ends` or `next page begins` rather than `same paragraph`, since that is all that is known, and the excerpt shows the reader in one line which case it is. The page read takes the same rule where it adds something: every chunk inside a read already sits beside the ones it adjoins, so only a read that shows the last chunk of its last page names anything — the next page's first chunk — and a read that stopped short already names the command that goes on. The costs, named rather than discovered. Emitting is minting, so the adjoining chunk is recorded as shown though only an excerpt of it was: it now binds `resolved` for a claim drawn from text the writer saw 160 characters of, the trade `search` already makes for every hit, and `ADJOINING_NOTE` says so once per run. It is noisy on clean documents: a read of any page of a paginated source but the last gains three lines and the note, and a hit on a page's first or last chunk gains its neighbour. The demo's own `read t12-summary p1`, a page ending on a full stop before a heading, now names `p2.c1`, which moves the walkthrough's `session show` from 77 anchors to 78; the gate tests' toy fixtures, one sentence a page, show it hardest. Display and ledger only everywhere else: no chunk boundary, token, anchor or receipt moves, and a hit or a read with nothing adjoining prints byte for byte what it did — pinned over the fakes, which now carry offsets the way the store does, and over a real ingest of a PDF whose paragraph the typesetter carried onto the next page. `EXCERPT_CHARS` and the excerpt cut moved from `searcher.py` to `reader.py` for the reason `LIST_HINT` lives there: both surfaces print the neighbour in one shape, and the searcher imports the reader, never the reverse. |
|
|
165
165
|
| 2026-09-17 | **A drifted citation whose words did not change is found where they stand now, by exact text and never by resemblance, and printing its new token is not minting it.** A re-ingested source re-chunks, so a paragraph inserted near the top of a page moves every chunk ordinal below it, and a citation whose words are untouched comes back `drifted` because a chunk's ordinal is its address — the Open list's oldest line, sharpened when `ingest` learned to announce a new generation (2026-08-20) and still left the agent a list of failures and a manual hunt per line. `backdraft locate <doc.md>` does the hunt: for each citation `citation_for` calls `drifted`, it looks the cited anchor's snippet hash up in the document's current generation (`Registry.anchors_with_snippet`, one query on the new `idx_anchors_snippet`, which the idempotent DDL adds to an existing registry on its next open) through `current_with`, `current_at`'s other half — that one keeps the locator and asks what text stands there, this keeps the text and asks which locators hold it. Matching is the normalized hash, same anchor kind, and nothing looser, because a near match proposed as a move rewrites provenance; the price is named rather than discovered — a sentence edited by one figure is `gone`, and so is a chunk that an insertion under 200 characters merged into (chunking rule 2), since its text did change. Three outcomes, and only one proposes: `moved` is exactly one place holding the words of a chunk or a page. Several places are `ambiguous` and named, because choosing among identical text is a guess. So is a cell with one match, which was the real decision: a cell's receipt is its value, and an equal value at another address is as often a second figure that agrees — two rates of 6.25% — as the same cell after a row went in, so a workbook gets lines naming the right cell for a person to confirm rather than tokens to paste. Read-only is the queued shape and the gate's doctrine argued against it, since every gate surface that hands a writer a token to cite mints it; it holds anyway, and the reason is that printing is not what makes a token citable — `bind` is. A moved token binds `not_shown` until the session is shown it, which is the ledger being right about names even where the words were seen, so the queued acceptance ("applying those tokens by hand and re-binding gives a clean run") holds one step later than written: the closing line names the `show` that records exactly the moved tokens `--session` has not seen (read, never created, and carried when typed through `gate.session_argument`), or the re-bind itself once none remain. Minting here was the alternative and was rejected, because a command that proposes should not write the ledger it proposes against, and `verify` already prints tokens without minting them. Two more departures from the queued shape, stated. It walks the document's own tokens (`kernel.claims.parse_claims`) rather than the stored `bindings`, which go stale the moment one token is swapped by hand and key on the path as it was typed. And it lives in `cli.py` beside the `ingest` whose note now names it, not in `bind/cli.py`, since that closing line needs the gate's session wording and bind may not import the gate. Exit 0 whatever it finds: `bind` is the check and this is the advice. Not Later's living documents, which is a cited-then-versus-now presentation; this is the lookup that presentation would stand on. |
|
|
166
|
+
| 2026-10-06 | **`bind` names the session it judged `not_shown` against, and a record bound in the default one says what that costs wherever it travels.** The 2026-08-31 row found that the default session accumulates every read any run in a registry ever made and fixed it at `session show`, the command that reports the ledger, leaving `bind`, the command that acts on it, to resolve the session in silence: `backdraft bind memo.md` with nothing exported judged against the shared ledger and its report never said so — in this repo's own demo that run binds clean with no `not_shown` at all, because development runs have read everything into `default`, which is exactly the false comfort the row described. The front-walk header now ends `against session <id>` and, where the id is `default`, the report closes with `gate.reader.DEFAULT_SESSION_NOTE` itself, imported rather than re-worded, since two wordings of one cost is how the two commands' warnings would drift; keyed on the id as `session show` keys it, so an explicit `--session default` is the same run byte for byte. Backfill names no session and gets no note, because it never consults the ledger and a warning that `bind` judges `not_shown` against all of it would be false there. The CLI does not travel and the record does, so the format says it too: `not_shown`'s legend entry and `spec/artifact.md`'s `session_id` row state that `default` means a ledger every run shares, and `verify`'s first tier prints a `session: default` line from the file alone, with no registry, so the recipient — the reader least able to learn it otherwise — is told. That sentence is `kernel.artifact.DEFAULT_SESSION_MEANING`, one string the legend embeds and `verify` prints, and the id itself moved into the kernel as `DEFAULT_SESSION` (re-exported by `cli_context` unchanged) because a session id the format assigns meaning to is format, not CLI vocabulary. The writer's note and the reader's sentence stay two strings on purpose: one says what to do before binding, the other what to conclude from a record already bound, and neither audience can act on the other's. The cost is the 2026-08-24 trade taken again — a legend change regenerates the golden sidecars and the demo artifact — and `verify --json` gained nothing, since the record it checks already carries `session_id` and the line is a reading of the record, not a finding. |
|
|
167
|
+
| 2026-10-07 | **What `forget` took out is listed by `ls --withdrawn`, a flag rather than a block under `ls`.** Withdrawal took a source off every surface that offers one, which is right, and left none that lists what went: `ls` in a registry holding two withdrawn documents said `no documents ingested`, and the only places a withdrawn document still appeared — `session show`'s mark, the JSON export, the unknown-slug error — are not where anybody looks. That mattered most for the undo, since re-ingesting is the way back and the path to re-ingest lives on the document, which is exactly what drops out of the list. The choice was a flag or a block printed under `ls` whenever the registry holds a withdrawal, and the block lost on the objection the item named: `ls` is the readable set, and anything that widens it risks making a withdrawn source look available — a footer under the live rows is one scroll from being read as more of them, and an agent parsing tab rows would have to learn to stop at it. So `ls --withdrawn` is a view of its own that lists only the withdrawn documents, never a marked row sorted among live ones, and default `ls` holds the byte-identity rule the source-naming work set: a registry with a withdrawal beside live sources prints exactly what the live sources alone print. The one place default `ls` does change is the place it was false: with nothing on offer and something withdrawn it says `no documents on offer: N withdrawn` and names the flag, instead of claiming an empty registry. Each withdrawn row is `ls`'s four fields then `withdrawn_reason` and `WITHDRAWN_HINT` verbatim, so the date and the way back keep their single owners rather than gaining a fourth wording, and it drops the `little text` mark, which advises a citation and nothing here is on offer to cite. The gate's own document list is untouched and pinned equal: the gate serves sources, and a withdrawn source is not one. The cost is discoverability — a flag is found by reading `--help` or the docs, not by running `ls` — which the `unresolved — withdrawn` guidance in the skill and `llms.txt` now pays by naming it at the moment a caller meets a withdrawal. |
|
|
168
|
+
| 2026-10-08 | **`ingest` says when `--slug` was not applied, and names the cost of a rename rather than a route to one, because there is no route.** `Registry.ingest` honours a slug only for a new document, a rule stated in its docstring and nowhere a caller reads: `backdraft ingest report.pdf --slug q4-report` against a file already ingested printed the old slug at exit 0 and never mentioned the flag, which is worst on a URL re-fetch, where the host-built fallback slug is exactly what a late `--slug` is trying to replace. The source's line now ends `--slug not applied` and one note names the slug the document keeps, in the shape `restored` set — a mark beside the outcome and a grouped note, not a third reporting shape — with no registry or schema change, since the CLI holds the requested slug and the returned `Ingested` carries the assigned one. The item as queued asked the note to offer `forget` and a re-ingest under the new slug as the costly way to re-slug; that path does not exist, because `_find_document` matches a withdrawn document by bytes and then origin and never by slug, so the re-ingest restores the same document under the same slug — the 2026-09-02 rule that makes re-ingest `forget`'s undo. Printing it would have been advice that cannot be taken, so the note says a rename would strand every token carrying the slug and that nothing renames a document, `forget` included, and a test pins that the forget-then-ingest run reports `restored --slug not applied`. Exit 0 throughout: getting back the document you already had is not a failure. A `--slug` on a new document and every ingest without one are byte-identical, pinned and checked against `demo/`'s sources. The dry run's own advice for a source not yet ingested — that changing a slug later means re-ingesting — was untouched here and was then the one surface that still implied a re-ingest renames; the release cleanup gave it this note's wording. |
|
|
166
169
|
|
|
167
170
|
## Open
|
|
168
171
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: backdraft
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.13.0
|
|
4
4
|
Summary: Drop-in provenance for factual claims: one click from any claim to the evidence behind it.
|
|
5
5
|
Project-URL: Homepage, https://backdraft.dev
|
|
6
6
|
Project-URL: Documentation, https://backdraft.dev/docs.html
|
|
@@ -92,7 +92,10 @@ than a page, so the host stands in — and a slug is permanent once tokens carry
|
|
|
92
92
|
it. `backdraft ingest <source> --dry-run` is how to see that before choosing:
|
|
93
93
|
it prints the slug and media type each source would take and stops, fetching
|
|
94
94
|
nothing and writing nothing, and it says when a source is already ingested or
|
|
95
|
-
when the name it wants is taken.
|
|
95
|
+
when the name it wants is taken. Permanent means `--slug` only ever names a new
|
|
96
|
+
document: on a source already ingested the line says `--slug not applied` and a
|
|
97
|
+
note names the slug it keeps — and `forget` then a re-ingest brings back that
|
|
98
|
+
same slug rather than a new one, since identity is the bytes, not the name.
|
|
96
99
|
|
|
97
100
|
Every ingest line ends with how much text came out, and says which of three
|
|
98
101
|
things happened: a document created, a **new generation** of one whose bytes
|
|
@@ -237,8 +240,9 @@ Anything counted there binds `resolved`; anything else in the registry binds
|
|
|
237
240
|
`not_shown`. Without an exported session, reads land in one default ledger every
|
|
238
241
|
run in the project shares and nothing ever resets — so `not_shown` weakens from
|
|
239
242
|
"this writer never saw it" to "nothing here ever has". `session show` says so at
|
|
240
|
-
exit 0 when you are in it,
|
|
241
|
-
|
|
243
|
+
exit 0 when you are in it, `bind` names the session on its first line and
|
|
244
|
+
closes with the same note when it judged against the default, and `backdraft
|
|
245
|
+
session start --id s-<name>` is the one command that fixes it.
|
|
242
246
|
|
|
243
247
|
**Cite** by writing the token as the href of a markdown link on the claim span.
|
|
244
248
|
Multiple citations are `;`-separated in one href.
|
|
@@ -252,7 +256,7 @@ Multiple citations are `;`-separated in one href.
|
|
|
252
256
|
|
|
253
257
|
```console
|
|
254
258
|
$ backdraft bind memo.md --session s-bridgeview --check value-trace,overlap
|
|
255
|
-
bound 17 claim(s), 18 citation(s) [frontwalk]
|
|
259
|
+
bound 17 claim(s), 18 citation(s) [frontwalk] against session s-bridgeview
|
|
256
260
|
resolved: 17
|
|
257
261
|
unresolved: 1
|
|
258
262
|
overlap: pass 13, skip 4
|
|
@@ -486,6 +490,19 @@ slug, not a second one beside it, so every token it ever minted resolves again.
|
|
|
486
490
|
The price of never actually removing anything is that a registry only grows; a
|
|
487
491
|
corpus you must be able to shrink is a new registry, not a `forget`.
|
|
488
492
|
|
|
493
|
+
`ls` lists what is on offer, so a withdrawn source leaves it too, and a registry
|
|
494
|
+
with nothing withdrawn prints exactly what it always did. `ls --withdrawn` is
|
|
495
|
+
where the rest are listed — each with the date it went and the ingest that
|
|
496
|
+
brings it back, which is the one place that path is still written down:
|
|
497
|
+
|
|
498
|
+
```console
|
|
499
|
+
$ backdraft ls --withdrawn
|
|
500
|
+
scratch-copy scratch-copy.md text 1 page withdrawn from the registry on 2026-10-07T13:03:11.922792Z Re-ingest it to bring it back: backdraft ingest scratch-copy.md
|
|
501
|
+
```
|
|
502
|
+
|
|
503
|
+
A registry whose every source has been withdrawn says so — `no documents on
|
|
504
|
+
offer: 2 withdrawn` — rather than calling itself empty.
|
|
505
|
+
|
|
489
506
|
## What lives where
|
|
490
507
|
|
|
491
508
|
A working directory stays clean: the authored document and its artifact are
|
|
@@ -532,6 +549,25 @@ Three extras:
|
|
|
532
549
|
`backdraft[vlm]` still installs: it is an empty compat alias from when the
|
|
533
550
|
vision deps were an extra.
|
|
534
551
|
|
|
552
|
+
**In an unfamiliar environment, run `backdraft doctor` first.** Everything
|
|
553
|
+
above degrades rather than fails, so without it each gap is discovered at the
|
|
554
|
+
moment it is missed. `doctor` reports all of them up front — the registry,
|
|
555
|
+
poppler, the vision model, `[xls]`, `[entail]` and `[math]` — each as `ready`
|
|
556
|
+
or `missing`, with what its absence costs the verbs and the command that fixes
|
|
557
|
+
it, in the same words the verb itself would print. Keys are named `set` or
|
|
558
|
+
`absent` and their values never printed. Read-only, and always exit 0: a
|
|
559
|
+
missing optional capability is not an error.
|
|
560
|
+
|
|
561
|
+
```console
|
|
562
|
+
$ backdraft doctor
|
|
563
|
+
registry ready /…/demo/.backdraft, 3 document(s)
|
|
564
|
+
page images ready poppler renders PDF pages for the artifact's cited-page view
|
|
565
|
+
vision model missing Glossy or scanned PDFs extract better through a vision model: set BACKDRAFT_VLM_API_KEY in .backdraft/env.
|
|
566
|
+
costs ingest: `auto` reads a PDF's text layer, so a scan comes back thin, and an image cannot be ingested at all. A PDF with a text layer, and every other format, is unaffected.
|
|
567
|
+
key BACKDRAFT_VLM_API_KEY absent
|
|
568
|
+
…
|
|
569
|
+
```
|
|
570
|
+
|
|
535
571
|
From a checkout:
|
|
536
572
|
|
|
537
573
|
```bash
|
|
@@ -65,7 +65,10 @@ than a page, so the host stands in — and a slug is permanent once tokens carry
|
|
|
65
65
|
it. `backdraft ingest <source> --dry-run` is how to see that before choosing:
|
|
66
66
|
it prints the slug and media type each source would take and stops, fetching
|
|
67
67
|
nothing and writing nothing, and it says when a source is already ingested or
|
|
68
|
-
when the name it wants is taken.
|
|
68
|
+
when the name it wants is taken. Permanent means `--slug` only ever names a new
|
|
69
|
+
document: on a source already ingested the line says `--slug not applied` and a
|
|
70
|
+
note names the slug it keeps — and `forget` then a re-ingest brings back that
|
|
71
|
+
same slug rather than a new one, since identity is the bytes, not the name.
|
|
69
72
|
|
|
70
73
|
Every ingest line ends with how much text came out, and says which of three
|
|
71
74
|
things happened: a document created, a **new generation** of one whose bytes
|
|
@@ -210,8 +213,9 @@ Anything counted there binds `resolved`; anything else in the registry binds
|
|
|
210
213
|
`not_shown`. Without an exported session, reads land in one default ledger every
|
|
211
214
|
run in the project shares and nothing ever resets — so `not_shown` weakens from
|
|
212
215
|
"this writer never saw it" to "nothing here ever has". `session show` says so at
|
|
213
|
-
exit 0 when you are in it,
|
|
214
|
-
|
|
216
|
+
exit 0 when you are in it, `bind` names the session on its first line and
|
|
217
|
+
closes with the same note when it judged against the default, and `backdraft
|
|
218
|
+
session start --id s-<name>` is the one command that fixes it.
|
|
215
219
|
|
|
216
220
|
**Cite** by writing the token as the href of a markdown link on the claim span.
|
|
217
221
|
Multiple citations are `;`-separated in one href.
|
|
@@ -225,7 +229,7 @@ Multiple citations are `;`-separated in one href.
|
|
|
225
229
|
|
|
226
230
|
```console
|
|
227
231
|
$ backdraft bind memo.md --session s-bridgeview --check value-trace,overlap
|
|
228
|
-
bound 17 claim(s), 18 citation(s) [frontwalk]
|
|
232
|
+
bound 17 claim(s), 18 citation(s) [frontwalk] against session s-bridgeview
|
|
229
233
|
resolved: 17
|
|
230
234
|
unresolved: 1
|
|
231
235
|
overlap: pass 13, skip 4
|
|
@@ -459,6 +463,19 @@ slug, not a second one beside it, so every token it ever minted resolves again.
|
|
|
459
463
|
The price of never actually removing anything is that a registry only grows; a
|
|
460
464
|
corpus you must be able to shrink is a new registry, not a `forget`.
|
|
461
465
|
|
|
466
|
+
`ls` lists what is on offer, so a withdrawn source leaves it too, and a registry
|
|
467
|
+
with nothing withdrawn prints exactly what it always did. `ls --withdrawn` is
|
|
468
|
+
where the rest are listed — each with the date it went and the ingest that
|
|
469
|
+
brings it back, which is the one place that path is still written down:
|
|
470
|
+
|
|
471
|
+
```console
|
|
472
|
+
$ backdraft ls --withdrawn
|
|
473
|
+
scratch-copy scratch-copy.md text 1 page withdrawn from the registry on 2026-10-07T13:03:11.922792Z Re-ingest it to bring it back: backdraft ingest scratch-copy.md
|
|
474
|
+
```
|
|
475
|
+
|
|
476
|
+
A registry whose every source has been withdrawn says so — `no documents on
|
|
477
|
+
offer: 2 withdrawn` — rather than calling itself empty.
|
|
478
|
+
|
|
462
479
|
## What lives where
|
|
463
480
|
|
|
464
481
|
A working directory stays clean: the authored document and its artifact are
|
|
@@ -505,6 +522,25 @@ Three extras:
|
|
|
505
522
|
`backdraft[vlm]` still installs: it is an empty compat alias from when the
|
|
506
523
|
vision deps were an extra.
|
|
507
524
|
|
|
525
|
+
**In an unfamiliar environment, run `backdraft doctor` first.** Everything
|
|
526
|
+
above degrades rather than fails, so without it each gap is discovered at the
|
|
527
|
+
moment it is missed. `doctor` reports all of them up front — the registry,
|
|
528
|
+
poppler, the vision model, `[xls]`, `[entail]` and `[math]` — each as `ready`
|
|
529
|
+
or `missing`, with what its absence costs the verbs and the command that fixes
|
|
530
|
+
it, in the same words the verb itself would print. Keys are named `set` or
|
|
531
|
+
`absent` and their values never printed. Read-only, and always exit 0: a
|
|
532
|
+
missing optional capability is not an error.
|
|
533
|
+
|
|
534
|
+
```console
|
|
535
|
+
$ backdraft doctor
|
|
536
|
+
registry ready /…/demo/.backdraft, 3 document(s)
|
|
537
|
+
page images ready poppler renders PDF pages for the artifact's cited-page view
|
|
538
|
+
vision model missing Glossy or scanned PDFs extract better through a vision model: set BACKDRAFT_VLM_API_KEY in .backdraft/env.
|
|
539
|
+
costs ingest: `auto` reads a PDF's text layer, so a scan comes back thin, and an image cannot be ingested at all. A PDF with a text layer, and every other format, is unaffected.
|
|
540
|
+
key BACKDRAFT_VLM_API_KEY absent
|
|
541
|
+
…
|
|
542
|
+
```
|
|
543
|
+
|
|
508
544
|
From a checkout:
|
|
509
545
|
|
|
510
546
|
```bash
|