diff --git a/AGENTS.md b/AGENTS.md index d10bff5..8126996 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -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 diff --git a/Cargo.lock b/Cargo.lock index 1f48969..d2e4997 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -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", diff --git a/Cargo.toml b/Cargo.toml index df11c86..dfc8621 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -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" diff --git a/crates/ledger-import/Cargo.toml b/crates/ledger-import/Cargo.toml index a6e690f..7c675ac 100644 --- a/crates/ledger-import/Cargo.toml +++ b/crates/ledger-import/Cargo.toml @@ -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 } diff --git a/crates/ledger-import/src/lib.rs b/crates/ledger-import/src/lib.rs index 4d479d1..9b7df87 100644 --- a/crates/ledger-import/src/lib.rs +++ b/crates/ledger-import/src/lib.rs @@ -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; diff --git a/crates/ledger-import/tests/extract.rs b/crates/ledger-import/tests/extract.rs index 20cbee6..28c6f2f 100644 --- a/crates/ledger-import/tests/extract.rs +++ b/crates/ledger-import/tests/extract.rs @@ -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. diff --git a/crates/ledger-reader/Cargo.toml b/crates/ledger-reader/Cargo.toml new file mode 100644 index 0000000..925b800 --- /dev/null +++ b/crates/ledger-reader/Cargo.toml @@ -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 diff --git a/crates/ledger-import/src/extract.rs b/crates/ledger-reader/src/lib.rs similarity index 100% rename from crates/ledger-import/src/extract.rs rename to crates/ledger-reader/src/lib.rs diff --git a/crates/ledger-store/Cargo.toml b/crates/ledger-store/Cargo.toml index 90887ea..0a97134 100644 --- a/crates/ledger-store/Cargo.toml +++ b/crates/ledger-store/Cargo.toml @@ -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" diff --git a/crates/ledger-store/src/store.rs b/crates/ledger-store/src/store.rs index f618bfa..eed4540 100644 --- a/crates/ledger-store/src/store.rs +++ b/crates/ledger-store/src/store.rs @@ -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, - /// 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, }) diff --git a/crates/ledger-store/tests/attachments.rs b/crates/ledger-store/tests/attachments.rs index 0f62b11..0366d6c 100644 --- a/crates/ledger-store/tests/attachments.rs +++ b/crates/ledger-store/tests/attachments.rs @@ -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()) diff --git a/docs/architecture.md b/docs/architecture.md index 0c630e7..e3ad2c0 100644 --- a/docs/architecture.md +++ b/docs/architecture.md @@ -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 diff --git a/docs/testing.md b/docs/testing.md index 2af0213..5fb0086 100644 --- a/docs/testing.md +++ b/docs/testing.md @@ -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`; diff --git a/fuzz/Cargo.lock b/fuzz/Cargo.lock index 467bb25..99e0016 100644 --- a/fuzz/Cargo.lock +++ b/fuzz/Cargo.lock @@ -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" diff --git a/plan/served.md b/plan/served.md index 8e857e6..4b6519c 100644 --- a/plan/served.md +++ b/plan/served.md @@ -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`