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

+1 -1
View File
@@ -160,7 +160,7 @@ clone, with status. Rules:
`company.timezone`: "today" is computed by the caller and passed in;
the core still reads no clock). New dependencies need justification in
the commit message. No I/O, no networking, no time-source in the core.
- Future crates (`ledger-store`, `ledger-import`, `ledger-oracle`, `books-app`)
- Future crates (`ledger-store`, `ledger-import`, `ledger-reader`, `ledger-oracle`, `books-app`)
depend inward toward the core, never the reverse. See `docs/architecture.md`.
## Working here
Generated
+14 -5
View File
@@ -3989,13 +3989,10 @@ dependencies = [
"chrono",
"csv",
"deunicode",
"hayro 0.8.0",
"hayro-interpret 0.8.0",
"kurbo",
"ledger-core",
"ledger-reader",
"proptest",
"qrcode",
"quick-xml 0.38.4",
"rqrr",
"rust_decimal",
"rust_xlsxwriter",
@@ -4058,6 +4055,18 @@ dependencies = [
"zip 9.0.2",
]
[[package]]
name = "ledger-reader"
version = "0.1.0"
dependencies = [
"hayro 0.8.0",
"hayro-interpret 0.8.0",
"kurbo",
"quick-xml 0.38.4",
"thiserror 2.0.21",
"zip 9.0.2",
]
[[package]]
name = "ledger-store"
version = "0.1.0"
@@ -4068,7 +4077,7 @@ dependencies = [
"getrandom 0.2.17",
"hex",
"ledger-core",
"ledger-import",
"ledger-reader",
"libc",
"proptest",
"rusqlite",
+1 -1
View File
@@ -1,6 +1,6 @@
[workspace]
resolver = "2"
members = ["crates/ledger-core", "crates/ledger-store", "crates/ledger-config", "crates/ledger-mcp", "crates/ledger-import", "crates/ledger-cli", "crates/ledger-gui", "crates/ledger-vault", "crates/ledger-host", "crates/ledger-package"]
members = ["crates/ledger-core", "crates/ledger-store", "crates/ledger-config", "crates/ledger-mcp", "crates/ledger-import", "crates/ledger-reader", "crates/ledger-cli", "crates/ledger-gui", "crates/ledger-vault", "crates/ledger-host", "crates/ledger-package"]
[workspace.package]
edition = "2024"
+3 -11
View File
@@ -18,17 +18,8 @@ typst = "0.15.1"
serde_json = "1"
typst-layout = "0.15.1"
qrcode = { version = "0.14.1", default-features = false, features = ["svg"] }
# Extraction (extract.rs, plan P6): a PDF's text, a page's pixels, an
# XLSX's cells. hayro (the renderer) and hayro-interpret (the text
# interpreter) are pure Rust and already the viewer's PDF reader, so the
# server and the viewer read a receipt through one implementation rather
# than two; `zip` and `quick-xml` read a spreadsheet's XML. None of them
# touches money: a number is kept as the decimal string the XML holds.
hayro = { version = "0.8", features = ["embed-fonts"] }
hayro-interpret = "0.8"
kurbo = "0.13"
zip = { version = "9.0.1", default-features = false, features = ["deflate"] }
quick-xml = "0.38"
# The reader (extract.rs moved out: D-61); re-exported as `extract`.
ledger-reader = { path = "../ledger-reader" }
# BSD-3-Clause: the IIF's transliteration to ASCII (iif.rs, rules.md B-34).
# Pinned exactly: a new table would change what a CPA's file says.
deunicode = { version = "=1.6.0", default-features = false }
@@ -38,4 +29,5 @@ workspace = true
[dev-dependencies]
proptest = "1"
zip = { version = "9.0.1", default-features = false, features = ["deflate"] }
rqrr = { version = "0.11.0", default-features = false }
+2 -1
View File
@@ -16,7 +16,8 @@
pub mod asset_register;
pub mod bank_csv;
pub mod chart_csv;
pub mod extract;
/// The one reader, now the `ledger-reader` crate (D-61), kept at its old path.
pub use ledger_reader as extract;
pub mod fonts;
pub mod iif;
pub mod invoice_pdf;
+1 -1
View File
@@ -1,7 +1,7 @@
//! The one extractor (plan P6 step 1): a PDF's text and pages, and an
//! XLSX's cells as exact decimal strings.
//!
//! Expected behavior comes from `crates/ledger-import/src/extract.rs`'s
//! Expected behavior comes from `crates/ledger-reader/src/lib.rs`'s
//! INVARIANTS, which come from plan/served.md P6 step 1 and D-15, D-32 and
//! D-53.
+23
View File
@@ -0,0 +1,23 @@
[package]
name = "ledger-reader"
version = "0.1.0"
edition = "2024"
license = "MIT OR Apache-2.0"
description = "The one reader: a PDF's text and pages, an XLSX's cells as exact decimal strings (plan P6, D-61)."
[dependencies]
thiserror = "2"
# Extraction (plan P6): a PDF's text, a page's pixels, an XLSX's cells.
# hayro (the renderer) and hayro-interpret (the text interpreter) are pure
# Rust and already the viewer's PDF reader, so the server and the viewer
# read a receipt through one implementation rather than two; `zip` and
# `quick-xml` read a spreadsheet's XML. None of them touches money: a
# number is kept as the decimal string the XML holds.
hayro = { version = "0.8", features = ["embed-fonts"] }
hayro-interpret = "0.8"
kurbo = "0.13"
zip = { version = "9.0.1", default-features = false, features = ["deflate"] }
quick-xml = "0.38"
[lints]
workspace = true
File renamed without changes.
+2 -2
View File
@@ -8,11 +8,11 @@ description = "Durable persistence for wanderbooks: a hash-chained JSONL journal
[dependencies]
ledger-core = { path = "../ledger-core" }
# The one extractor (plan P6 step 1, D-53): the writer journals a file's
# The one reader (plan P6 step 1, D-53, D-61: its own crate): the writer journals a file's
# text at attach time and on `wanderbooks books extract`, so the extractor
# must be callable here rather than only from the server. It is pure and
# deterministic (no I/O, no network, no time source).
ledger-import = { path = "../ledger-import" }
ledger-reader = { path = "../ledger-reader" }
rusqlite = { version = "0.32", features = ["bundled"] }
serde = { version = "1", features = ["derive"] }
serde_json = "1"
+6 -6
View File
@@ -45,7 +45,7 @@ use ledger_core::{
payroll::{PayrollAccounts, payroll_entry_plan, payroll_remit_plan},
reports,
};
use ledger_import::extract::{EXTRACTOR as E_EXTRACTOR, VERSION as E_VERSION};
use ledger_reader::{EXTRACTOR as E_EXTRACTOR, VERSION as E_VERSION};
use rusqlite::{Connection, OptionalExtension, params};
use rust_decimal::Decimal;
use serde_json::{Value, json};
@@ -368,7 +368,7 @@ pub struct Extraction {
pub attachment_id: u64,
/// The text's blob (its SHA-256), when there is text.
pub text_sha256: Option<String>,
/// What read it (`ledger_import::extract::EXTRACTOR`).
/// What read it (`ledger_reader::EXTRACTOR`).
pub extractor: String,
/// That extractor's version.
pub version: String,
@@ -4620,7 +4620,7 @@ impl Store {
if let Some(already) = self.extraction_for(attachment_id, E_EXTRACTOR, E_VERSION)? {
return Ok(already);
}
let got = ledger_import::extract::extract(bytes, mime_type, file_name);
let got = ledger_reader::extract(bytes, mime_type, file_name);
let text_sha = match &got.text {
Some(text) => Some(self.blobs.put(text.as_bytes())?.0),
None => None,
@@ -4644,9 +4644,9 @@ impl Store {
version: E_VERSION.into(),
pages: got.pages,
status: match got.status {
ledger_import::extract::ExtractStatus::Ok => ExtractionStatus::Ok,
ledger_import::extract::ExtractStatus::None => ExtractionStatus::None,
ledger_import::extract::ExtractStatus::Failed => ExtractionStatus::Failed,
ledger_reader::ExtractStatus::Ok => ExtractionStatus::Ok,
ledger_reader::ExtractStatus::None => ExtractionStatus::None,
ledger_reader::ExtractStatus::Failed => ExtractionStatus::Failed,
},
seq: line.seq,
})
+2 -2
View File
@@ -1471,8 +1471,8 @@ fn extraction_is_journaled_once_per_version() {
let x = s.extraction(id).unwrap().expect("an extraction record");
assert_eq!(x.status, ExtractionStatus::Ok);
assert_eq!(x.pages, 1);
assert_eq!(x.extractor, ledger_import::extract::EXTRACTOR);
assert_eq!(x.version, ledger_import::extract::VERSION);
assert_eq!(x.extractor, ledger_reader::EXTRACTOR);
assert_eq!(x.version, ledger_reader::VERSION);
assert_eq!(
s.extraction_text(id).unwrap().as_deref(),
Some(String::from_utf8_lossy(text).as_ref())
+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`;
+270 -19
View File
@@ -29,6 +29,21 @@ dependencies = [
"memchr",
]
[[package]]
name = "alloc-no-stdlib"
version = "2.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cc7bb162ec39d46ab1ca8c77bf72e890535becd1751bb45f64c597edb4c8c6b3"
[[package]]
name = "alloc-stdlib"
version = "0.2.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0e76a019e91224d279006ff972f1e984179a6e9feb050adba6ce8274aef23195"
dependencies = [
"alloc-no-stdlib",
]
[[package]]
name = "android_system_properties"
version = "0.1.6"
@@ -223,6 +238,27 @@ dependencies = [
"syn 3.0.9",
]
[[package]]
name = "brotli"
version = "8.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5cc91aac060a7a1e25823bdccbfb6af1875b88f17c6daac97894eed8207166b3"
dependencies = [
"alloc-no-stdlib",
"alloc-stdlib",
"brotli-decompressor",
]
[[package]]
name = "brotli-decompressor"
version = "5.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3a32acac15fe1967bc3986b2a6347dffc965602354ea6f450ad07e8bfd253583"
dependencies = [
"alloc-no-stdlib",
"alloc-stdlib",
]
[[package]]
name = "bumpalo"
version = "3.20.3"
@@ -753,6 +789,12 @@ version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b97b65636e5b9ef369943878ac74335ba1c55c1cb6adbf1e2c293c624248d693"
[[package]]
name = "fearless_simd"
version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f4beca3cb2444e3304ac30843cc091f44ed58932353cd492ce740067bfce6b12"
[[package]]
name = "fearless_simd"
version = "1.1.0"
@@ -806,6 +848,15 @@ dependencies = [
"bytemuck",
]
[[package]]
name = "font-types"
version = "0.12.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "835ae9d781464a79026cde387f9cbc650c9a4909d7cf7e658b127e3c35d2f35d"
dependencies = [
"bytemuck",
]
[[package]]
name = "fontconfig-parser"
version = "0.5.8"
@@ -902,8 +953,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "300e883d756b2e4ec94e02791f39b04b522276138852cfc41d9fb7e904106099"
dependencies = [
"cfg-if",
"js-sys",
"libc",
"r-efi 6.0.0",
"wasm-bindgen",
]
[[package]]
@@ -934,11 +987,28 @@ dependencies = [
"log",
"peniko",
"png",
"skrifa",
"skrifa 0.42.1",
"smallvec",
"vello_common 0.0.9",
]
[[package]]
name = "glifo"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9f1218dedd5348c1b166ec3b8b3cbca49d2f527bd43b3fa072ea5123c523ed3f"
dependencies = [
"bytemuck",
"foldhash",
"hashbrown 0.17.1",
"log",
"peniko",
"png",
"skrifa 0.44.0",
"smallvec",
"vello_common 0.3.0",
]
[[package]]
name = "guillotiere"
version = "0.7.0"
@@ -1016,11 +1086,27 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c4caa128ab87fd48ffb7490617cf93f77f606820dffbc9fd9ef6ab0ed077f56d"
dependencies = [
"bytemuck",
"hayro-interpret",
"hayro-interpret 0.7.0",
"image",
"kurbo",
"pic-scale",
"vello_cpu",
"vello_cpu 0.0.8",
]
[[package]]
name = "hayro"
version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4675ff5acd7ca41c0c34ccd2a3a154f5c86d8b5ec92bef0a114bfb194969e2af"
dependencies = [
"bytemuck",
"fearless_simd 1.1.0",
"hayro-interpret 0.8.0",
"image",
"kurbo",
"pic-scale",
"rustc-hash",
"vello_cpu 0.3.0",
]
[[package]]
@@ -1041,6 +1127,7 @@ version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "24d285dc30731c8485de5fa732fbdf2b3affdf01e4da7c2135022ca6fa664bf6"
dependencies = [
"brotli",
"hayro-postscript",
]
@@ -1052,13 +1139,32 @@ checksum = "f2613d0406898995042d0794c4245b2f2fba1246490c8ac593769bba6551129d"
dependencies = [
"bitflags 2.13.2",
"hayro-cmap",
"hayro-syntax",
"hayro-syntax 0.7.2",
"kurbo",
"moxcms",
"phf",
"rustc-hash",
"siphasher",
"skrifa",
"skrifa 0.42.1",
"smallvec",
"yoke",
]
[[package]]
name = "hayro-interpret"
version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ac9df074ff163a63864cec9b0bf640ccbff8abf4e44481df7369ce0ac172ffbe"
dependencies = [
"bitflags 2.13.2",
"hayro-cmap",
"hayro-syntax 0.8.0",
"kurbo",
"moxcms",
"phf",
"rustc-hash",
"siphasher",
"skrifa 0.46.2",
"smallvec",
"yoke",
]
@@ -1082,6 +1188,15 @@ dependencies = [
"fearless_simd 0.4.1",
]
[[package]]
name = "hayro-jpeg2000"
version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "122c5f834c37a2260b9c851bb8be685cf539b2a8b94b704a931ef7d2525d5835"
dependencies = [
"fearless_simd 1.1.0",
]
[[package]]
name = "hayro-postscript"
version = "0.1.0"
@@ -1095,7 +1210,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7434c89e920d84e077214a29e69b462a6518754610f732f1124af3948410cb8c"
dependencies = [
"base64 0.22.1",
"hayro-interpret",
"hayro-interpret 0.7.0",
"image",
"kurbo",
"siphasher",
@@ -1111,7 +1226,23 @@ dependencies = [
"flate2",
"hayro-ccitt 0.3.0",
"hayro-jbig2",
"hayro-jpeg2000",
"hayro-jpeg2000 0.3.5",
"memchr",
"rustc-hash",
"smallvec",
"zune-jpeg",
]
[[package]]
name = "hayro-syntax"
version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6cdc818d8d265dcb42081d488f7599df4f73fb79e9d347ecedcd3c23809d2026"
dependencies = [
"flate2",
"hayro-ccitt 0.4.0",
"hayro-jbig2",
"hayro-jpeg2000 0.4.1",
"memchr",
"rustc-hash",
"smallvec",
@@ -1125,7 +1256,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a0b3db92c4917aad24ff30d61413a9a7509ae88b27aee1ae34eff881e1d2723a"
dependencies = [
"flate2",
"hayro-syntax",
"hayro-syntax 0.7.2",
"pdf-writer",
]
@@ -1553,7 +1684,7 @@ dependencies = [
"rustc-hash",
"rustybuzz",
"siphasher",
"skrifa",
"skrifa 0.42.1",
"smallvec",
"subsetter",
"tiny-skia-path",
@@ -1572,7 +1703,7 @@ dependencies = [
"fontdb",
"krilla",
"resvg",
"skrifa",
"skrifa 0.42.1",
"smallvec",
"tiny-skia",
"usvg",
@@ -1638,6 +1769,7 @@ dependencies = [
"csv",
"deunicode",
"ledger-core",
"ledger-reader",
"qrcode",
"rust_decimal",
"rust_xlsxwriter",
@@ -1650,6 +1782,18 @@ dependencies = [
"typst-pdf",
]
[[package]]
name = "ledger-reader"
version = "0.1.0"
dependencies = [
"hayro 0.8.0",
"hayro-interpret 0.8.0",
"kurbo",
"quick-xml 0.38.4",
"thiserror",
"zip 9.1.3",
]
[[package]]
name = "ledger-store"
version = "0.1.0"
@@ -1659,6 +1803,7 @@ dependencies = [
"getrandom 0.2.17",
"hex",
"ledger-core",
"ledger-reader",
"libc",
"rusqlite",
"rust_decimal",
@@ -1808,6 +1953,18 @@ version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "13d2233c9842d08cfe13f9eac96e207ca6a2ea10b80259ebe8ad0268be27d2af"
[[package]]
name = "nix"
version = "0.31.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cf20d2fde8ff38632c426f1165ed7436270b44f199fc55284c38276f9db47c3d"
dependencies = [
"bitflags 2.13.2",
"cfg-if",
"cfg_aliases",
"libc",
]
[[package]]
name = "num-bigint"
version = "0.4.8"
@@ -2312,7 +2469,29 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c4ed38b89c2c77ff968c524145ad65fb010f38af5c7a224b53b81d47ac2daa81"
dependencies = [
"bytemuck",
"font-types",
"font-types 0.11.3",
]
[[package]]
name = "read-fonts"
version = "0.41.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "046a7d674daf459825b32f5062056d6882db0d2f5a479fbd76ccfc870ac18709"
dependencies = [
"bytemuck",
"font-types 0.12.7",
"once_cell",
]
[[package]]
name = "read-fonts"
version = "0.43.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "005c8acf251756c478b0bf402885bfd88a1476020c4c7e6060edc1aa68da38ea"
dependencies = [
"bytemuck",
"font-types 0.12.7",
"once_cell",
]
[[package]]
@@ -2447,7 +2626,7 @@ version = "0.90.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2be778223b36bb449b2ef2df4856ced2d311680818a7310db5c5dc370170f935"
dependencies = [
"zip",
"zip 4.6.1",
]
[[package]]
@@ -2637,7 +2816,27 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0c34617370ae968efb7161bb2beb517d9084659aae19e24b89e3db25b46e4564"
dependencies = [
"bytemuck",
"read-fonts",
"read-fonts 0.39.2",
]
[[package]]
name = "skrifa"
version = "0.44.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "819ab7d62b1d3e72d9d9dea5650bac30424f9111364bb94928dbf5ecad1baa68"
dependencies = [
"bytemuck",
"read-fonts 0.41.0",
]
[[package]]
name = "skrifa"
version = "0.46.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d4febebda507fb889c545a37a135517769d1391941013561292897961d1887d6"
dependencies = [
"bytemuck",
"read-fonts 0.43.3",
]
[[package]]
@@ -2724,7 +2923,7 @@ checksum = "38803281d1c23166c5ebcb455439a5d2afe711cc909cf88af72448c297756ad6"
dependencies = [
"kurbo",
"rustc-hash",
"skrifa",
"skrifa 0.42.1",
"write-fonts",
]
@@ -3015,6 +3214,12 @@ version = "2.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6af6ae20167a9ece4bcb41af5b80f8a1f1df981f6391189ce00fd257af04126a"
[[package]]
name = "typed-path"
version = "0.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9ce898823d8cd0f028662d2f349694b1af0a5cc9be83f1d213a00d3147445e1e"
[[package]]
name = "typenum"
version = "1.20.1"
@@ -3164,7 +3369,7 @@ dependencies = [
"fontdb",
"glidesort",
"hayagriva",
"hayro-syntax",
"hayro-syntax 0.7.2",
"icu_properties",
"image",
"indexmap",
@@ -3281,7 +3486,7 @@ dependencies = [
"comemo",
"ecow",
"flate2",
"hayro",
"hayro 0.7.1",
"hayro-svg",
"image",
"indexmap",
@@ -3558,6 +3763,22 @@ dependencies = [
"thiserror",
]
[[package]]
name = "vello_common"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "73c2fbe53548bcd25c8ff42f75a93770a8611a16b37bee56d7ef7b44e36027f1"
dependencies = [
"bytemuck",
"fearless_simd 0.7.0",
"guillotiere",
"log",
"peniko",
"png",
"smallvec",
"thiserror",
]
[[package]]
name = "vello_cpu"
version = "0.0.8"
@@ -3565,12 +3786,25 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6d8ded630e8316bb94a55881256506d1f3b9947b5f66db8a7d32ca7ba02decd0"
dependencies = [
"bytemuck",
"glifo",
"glifo 0.1.1",
"hashbrown 0.17.1",
"png",
"vello_common 0.0.8",
]
[[package]]
name = "vello_cpu"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ed1fec424fbba6acf6cbc78bb526447eb77590d95f40419d598870fe37535212"
dependencies = [
"bytemuck",
"glifo 0.4.0",
"hashbrown 0.17.1",
"png",
"vello_common 0.3.0",
]
[[package]]
name = "version_check"
version = "0.9.5"
@@ -3807,11 +4041,11 @@ version = "0.48.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cb731d4c4d93eacc69a1ad2f270f905788a98e4a3438267bcafbe08d3431c8d8"
dependencies = [
"font-types",
"font-types 0.11.3",
"indexmap",
"kurbo",
"log",
"read-fonts",
"read-fonts 0.39.2",
]
[[package]]
@@ -3962,6 +4196,23 @@ dependencies = [
"zopfli",
]
[[package]]
name = "zip"
version = "9.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1156b39d142a34b999c2310a50455e0cb3760bf7efad0122f796d813bee26ccb"
dependencies = [
"crc32fast",
"flate2",
"getrandom 0.4.3",
"indexmap",
"memchr",
"nix",
"typed-path",
"windows-sys",
"zopfli",
]
[[package]]
name = "zlib-rs"
version = "0.6.8"
+1 -1
View File
@@ -1299,7 +1299,7 @@ Each unit follows the 8.0.4 template. "A-n" is an owner answer numbered in 8.3.1
5. **Files:** owns `crates/ledger-reader/`; touches `crates/ledger-import/{Cargo.toml,src/lib.rs}`, `crates/ledger-store/Cargo.toml`, `crates/ledger-store/src/store.rs` (imports only), `Cargo.toml` (workspace members), the two fuzz targets' manifest, `docs/architecture.md`, `docs/testing.md`.
6. **Tests and proofs:** no new test: the move is behavior-preserving, and P6's `extraction_deterministic`, `xlsx_cells_are_decimal_strings`, `a_pdf_that_crashes_the_reader_is_failed_not_fatal` move with the module and pass unchanged; `fuzz_extract_pdf` and `fuzz_extract_xlsx` build. No kernel, Kani or Lean change.
7. **Done when:** (1) `cargo tree -p ledger-store -e normal` shows no `typst*` and no `ledger-import`; (2) `cargo tree -p ledger-import -e normal` shows no `ledger-store` (as today); (3) the three P6 extraction tests and the two fuzz targets pass where they now live; (4) `docs/architecture.md` has no "under review" sentence; (5) the batch's gate (D-60) green.
8. **As built:** none yet.
8. **As built:** `extract.rs` moved whole (`git mv`) to `crates/ledger-reader/src/lib.rs`; the new crate depends on `thiserror`, `hayro`, `hayro-interpret`, `kurbo`, `zip`, `quick-xml` only. `ledger-import` drops those (keeps `zip` as a dev-dependency for its test) and re-exports the crate with `pub use ledger_reader as extract;`, so `ledger_import::extract::*` still works for `ledger-gui`, `ledger-mcp`, `ledger-host` and the two fuzz targets (no caller changed). `ledger-store` depends on `ledger-reader` instead of `ledger-import`; `store.rs` and `tests/attachments.rs` use `ledger_reader::`. The three P6 tests stay in `crates/ledger-import/tests/extract.rs` (they build their PDFs with `ledger-import`'s statement writer; moving them would need a dev-dependency cycle) and pass through the re-export; the two unit tests moved with the module. `docs/architecture.md`, `docs/testing.md`, `AGENTS.md` updated.
#### P4b S4: `documents_v2`