refactor(reader): extract.rs moves into ledger-reader (U0d)

D-61 (Q-9): the writer sheds the typesetter and the crate map is layered
again. The PDF/XLSX reader (extract.rs, plan P6) is now the plain library
crate ledger-reader, moved whole with git mv and no behavior change.
ledger-store depends on it instead of ledger-import, so the store no
longer builds Typst or the fonts; ledger-import re-exports it as
ledger_import::extract so gui, mcp, host and the fuzz targets compile
unchanged, and still never depends on the store. New crate carries only
the dependencies extract.rs imports (hayro, hayro-interpret, kurbo, zip,
quick-xml, thiserror); fuzz/Cargo.lock gains the new package.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HS8WZziDnyxunC23MUxrLp
This commit is contained in:
swaitsandClaude Fable 5.1 committed 2026-10-29 12:45:14 -06:00
1 parent f98471516e
commit bb1eb84200
15 files changed
+344 -68

No files matched your search

+14 -14
View File
@@ -31,8 +31,9 @@ the specification in `bookspec/` (which never depends on the engine).
| `ledger-core` | the semantics engine: money, chart, journal, `Ledger` commands, reports, periods, assets, schedules, parties, A/R and A/P, payroll, projects, settings and conventions, the kernel and its Kani harnesses | nothing |
| `ledger-vault` | the TIN vault: XChaCha20-Poly1305 records under the book's key | nothing |
| `ledger-config` | the `wanderbooks.toml` codec (adoption in, `books.export_settings` out) and the receipt policy until P4 moves it into the core | `ledger-core` |
| `ledger-store` | the source of truth: the hash-chained JSONL journal, its format gate, the SQLite projection (schema 18; a file per schema), the blob store, backups, the access log, `Snapshot` for readers | `ledger-core`, `ledger-import` (the file reader, P6; `ledger-reader` after U0d, D-61) |
| `ledger-import` | formats: QuickBooks CSV in; IIF, XLSX, Typst PDFs (statements, the invoice, labels) out | `ledger-core` |
| `ledger-store` | the source of truth: the hash-chained JSONL journal, its format gate, the SQLite projection (schema 18; a file per schema), the blob store, backups, the access log, `Snapshot` for readers | `ledger-core`, `ledger-reader` (P6, D-61) |
| `ledger-reader` | the one reader: a PDF's text and pages, an XLSX's cells as exact decimal strings, plain text (P6; a plain library, in-process, D-61); `ledger-import` re-exports it as `extract` | none (`hayro`, `zip`, `quick-xml`) |
| `ledger-import` | formats: QuickBooks CSV in; IIF, XLSX, Typst PDFs (statements, the invoice, labels) out | `ledger-core`, `ledger-reader` |
| `ledger-package` | the shared read-only view over `Store` or `Snapshot` (`PackageBooks`): the CPA package, the 1099 worksheet, the invoice document, Needs attention | `ledger-core`, `ledger-store`, `ledger-config`, `ledger-import`, `ledger-vault` |
| `ledger-mcp` | the books server: the MCP tools (122), resources, the handbook, scopes, the door and the access log, backups' scheduling | all of the above |
| `ledger-host` | the host and its leases, the agent bridge (`wanderbooks mcp`), tokens in the OS keyring, the owner's commands, uploads and downloads | `ledger-store`, `ledger-mcp`, `ledger-config` |
@@ -50,14 +51,13 @@ test `crates/ledger-core/tests/oracle_gnucash.rs` with `oracle/gnucash/`
(`PackageBooks` is the shared view; extraction is in-process in
`ledger-import`, P6).
Decided (Q-9 → D-61, 2026-10-29): P6 made `ledger-store` depend on
`ledger-import` so the writer can extract a file's text at attach time
and on the backfill command. That keeps the direction inward
(`ledger-import` depends on the core alone and must never depend on the
store) but pulls Typst, the fonts and hayro into the store's dependency
closure. Unit U0d (plan 8.2, after S4.5) moves the reader into a small
library crate, `ledger-reader`, that both depend on; until it lands the
arrow stands as drawn above.
Decided and done (Q-9 → D-61, unit U0d): P6 had made `ledger-store` depend
on `ledger-import` so the writer can extract a file's text at attach time,
which pulled Typst, the fonts and hayro into the store's dependency
closure. The reader now lives in the small library crate `ledger-reader`;
`ledger-store` and `ledger-import` both depend on it (`ledger-import`
re-exports it as `extract`), the store no longer builds the typesetter,
and `ledger-import` still never depends on the store.
`ledger-core` itself (`crates/ledger-core/src/`): `money`, `account`,
`journal`, `ledger` (the command surface and replay), `kernel` (every
@@ -117,13 +117,13 @@ the agent's cannot drift apart. The crate depends inward (`ledger-core`,
`ledger-store`, `ledger-config`, `ledger-import`, `ledger-vault`), never on
`ledger-mcp` or `ledger-gui`.
## The evidence pipeline (`ledger-import`, `ledger-core`, `ledger-store`, P6)
## The evidence pipeline (`ledger-reader`, `ledger-core`, `ledger-store`, P6)
One extractor lives in `ledger-import` (`src/extract.rs`, D-53: in-process,
no new crate): a PDF's text and pages, an XLSX's cells as exact decimal
One extractor lives in `ledger-reader` (`src/lib.rs`, D-53, D-61: in-process,
a plain library): a PDF's text and pages, an XLSX's cells as exact decimal
strings, and plain text; the caps are 25 MiB in, 30 pages and 2 MiB of
text, and a reader panic is caught there. `ledger-store` depends on
`ledger-import` so the writer can read a file as it is filed and journal
`ledger-reader` so the writer can read a file as it is filed and journal
`attachment.text_extracted` (content-addressed text blob, extractor,
version, pages, `ok`/`none`/`failed`), one record per attachment, extractor
and version; `Store::verify` checks the text blob's hash and never
+4 -4
View File
@@ -3273,8 +3273,8 @@ check, and the preview's clone; attention's year comparison and its
## P6: the evidence pipeline (2026-10-27)
Extraction, dedup, annotations and company record categories (plan P6;
rules.md B-44). One in-process extractor in `ledger-import`
(`src/extract.rs`): `pdf_text` and `pdf_lines` (promoted from the PDF
rules.md B-44). One in-process extractor in `ledger-reader`
(`crates/ledger-reader/src/lib.rs`): `pdf_text` and `pdf_lines` (promoted from the PDF
reader test), `page_count`, `page_png` (promoted from the viewer's private
rasterizer), `xlsx_text` (cells as the exact decimal strings the sheet XML
holds), `text_of` (moved from `tin_files`, now reading a PDF's text too)
@@ -3284,13 +3284,13 @@ reaching the server (D-32: no sandbox, and the plan says so). The viewer
and the server's filing scan both read through it: `ledger-gui/src/receipts.rs`
keeps no `hayro` use of its own.
- **ledger-import:** `tests/extract.rs` `extraction_deterministic` (same
- **ledger-reader (tests stay in `crates/ledger-import/tests/extract.rs`, which builds its PDFs with `ledger-import`):** `extraction_deterministic` (same
bytes, same text and PNG bytes; plain text and an image's `none`),
`xlsx_cells_are_decimal_strings` (proptest: the cell text is the decimal
string written, never a float), `a_pdf_that_crashes_the_reader_is_failed_not_fatal`
(garbage and truncated PDFs are `Err`, the too-large cap is refused
before the reader, page 31 and a zero-size page are refused); unit tests
in `src/extract.rs` for the panic catch and the input cap. `tests/spec_import.rs`
in `crates/ledger-reader/src/lib.rs` for the panic catch and the input cap. `tests/spec_import.rs`
keeps its script/RTL PDF test; its `pdf_lines` helper is now the
extractor's.
- **ledger-core:** `src/evidence.rs` (`Annotation`, `AnnotationFields`;