S4.1 gave documents a number, terms, line accounts and snapshots, and S4.2
gave parties an email and default terms, but the store journaled none of
them: a document carrying one was refused by a temporary guard, because a
public write that replay loses would break I-3. This slice makes the
journal carry them.
- `invoice.created` and `bill.created` write a number, terms, bill-to and
seller snapshots, references, memo and a duplicate's reason, and each
line its account, quantity and unit price, every key only when set. A
document naming none is journaled byte for byte as every earlier version
wrote it (`no_new_fields_journals_byte_for_byte`), so the owner's live
books keep working.
- All of it is behind `documents_v2` (B-27): a write that names one waits
for the owner's upgrade, refused with `format_upgrade_pending` and
journaling nothing; a write that names none never waits
(`documents_v2_waits_for_the_upgrade`).
- `invoicing.first_number` is settable only while no numbered invoice
exists (A-2, B-40): the series has begun, and a number is never reused or
renumbered. Refused at `set_setting`, before anything is journaled.
- The issued copy is filed in the issuing call (A-7, B-40 item 3):
`create_invoice_issued` journals the entry, the invoice, then
`receipt.attached` on that entry (`INV-0007.pdf`, `application/pdf`,
"Issued invoice") and `attachment.linked` to the invoice. There is no
`issued_pdf` field: the attachment id is allocated after the entry exists
(CD-A6), and the link is how the copy is found and how its SHA-256 is
checked. Every refusal happens before the first journal line; the PDF's
bytes are stored before it, so a refused issue leaves an unreferenced,
deduplicated blob at worst.
- A preview runs the issue on a clone and journals nothing: the posting,
the total, the number it would take and the due date (`preview_invoice`,
`preview_bill`). Books that haven't upgraded keep the legacy shape: the
copy is filed as "Invoice {id}.pdf" and no number is taken (migration
item 5).
- Schema 16 adds the document columns; a line's new fields ride
`lines_json` as its project does. `differs_from` already compares whole
invoices, bills and parties, so it sees every new field.
8.0.6: (1) `books_written_under_older_rules_still_open` hand-writes both
new document shapes through JournalWriter and still opens; (2) a new
fixture `old-books/p4_s43_documents` was written by main d5385a7e (S4.2,
the version before this slice) through the old-books-writer harness, with
its expected.json; this version opens it and reports the same figures;
(3) see the note on R-A step 4 in the PR (the over-the-wire document write
needs S4.4's arguments; S4.2's invoicing-setting entry already covers the
feature's cut-over); (4) `documents_v2_is_its_own_feature` is green; (5) no
figure moves: no golden changed; (6) merge does not need the owner's
go-ahead; the live books refuse the new writes until he gives it.
just check green (no kernel function, Lean theorem or Ledger command, so no
`just prove` change).
55 lines
1.9 KiB
Rust
55 lines
1.9 KiB
Rust
//! `ledger-store`: durable persistence for the books.
|
|
//!
|
|
//! Three pieces, one job — keep the books safe:
|
|
//!
|
|
//! * [`journal`] — the source of truth. An append-only, hash-chained JSONL
|
|
//! journal. Every state change is an immutable fact, fsync'd on append.
|
|
//! * [`blob`] — the bytes. Content-addressed (SHA-256), immutable,
|
|
//! deduplicated receipt and document storage.
|
|
//! * [`Store`] — the SQLite projection plus the write path. SQLite is the
|
|
//! query engine and is disposable by design: [`Store::rebuild`] drops it
|
|
//! and replays the journal. [`Store::verify`] replays the journal into a
|
|
//! fresh engine and diffs its reports against both the live ledger and
|
|
//! the projection.
|
|
//!
|
|
//! The write path is singular: **validate → journal → mutate → apply**.
|
|
//! Journal first means a failed write leaves nothing behind; a failed
|
|
//! SQLite apply leaves the journal ahead, and the next open heals it via
|
|
//! `last_applied_seq`. There is exactly one writer: `Store` is `!Sync` by
|
|
//! construction (it owns a `rusqlite::Connection`).
|
|
|
|
pub mod access;
|
|
pub mod auth;
|
|
pub mod backup;
|
|
pub mod blob;
|
|
pub mod chain;
|
|
pub mod error;
|
|
pub mod exports;
|
|
pub mod form1099;
|
|
pub mod format;
|
|
pub mod journal;
|
|
pub mod meta;
|
|
pub mod ops;
|
|
pub mod passphrase;
|
|
pub mod reconcilable;
|
|
#[cfg(test)]
|
|
mod refuse;
|
|
pub mod root;
|
|
pub mod saving;
|
|
pub mod schema;
|
|
pub mod snapshot;
|
|
pub mod store;
|
|
pub mod uploads;
|
|
|
|
pub use blob::BlobStore;
|
|
pub use error::StoreError;
|
|
pub use format::{FormatUpgrade, JournalFormat, SUPPORTED_JOURNAL_FORMAT};
|
|
pub use journal::{GENESIS_PREV, JournalLine, JournalWriter};
|
|
pub use snapshot::{DiskCheck, Snapshot};
|
|
pub use store::{
|
|
Attachment, BillPreview, DepreciationRun, DisposalPreview, EntryRow, EventRow, InvoicePreview,
|
|
JOURNAL_FILE, LEGACY_DB_FILE, LOCK_FILE, MAX_RECURRING_RUN, PayrollLineInput, Reconciliation,
|
|
Statement, Store, Subject, VerifyReport, database_in_use, db_file, db_file_for,
|
|
is_database_file,
|
|
};
|