From 7c5a2fe3ce5ddc7b2c4d6e3d06c45338cd281130 Mon Sep 17 00:00:00 2001 From: Stephen Waits Date: Tue, 27 Oct 2026 07:19:57 -0600 Subject: [PATCH] docs: sent tracking in the rules, parity and test records (P4b S5) rules.md B-27 lists `documents_v2`, the one feature S4 to S7 share (D-52), including `invoice.sent`; receipt-store.md lists the event; parity.md gains the sent-tracking row (done: implemented, tested, and golden-covered by the trial balance of the fixture book around a send); testing.md records the tests and what P12 must mutate, and names the two attention predicates as deferred with step 4 until U0b lands. --- docs/parity.md | 12 ++++++++++ docs/receipt-store.md | 6 +++-- docs/spec/rules.md | 7 ++++-- docs/testing.md | 51 +++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 72 insertions(+), 4 deletions(-) diff --git a/docs/parity.md b/docs/parity.md index e27be0b..50450f8 100644 --- a/docs/parity.md +++ b/docs/parity.md @@ -45,6 +45,7 @@ No other statuses. | 1099 vendor tracking + year-end worksheet | Vendors → Track payments for 1099; 1099 report | partial | hand-verified fixture (bill payments, card exclusion, reversal, year boundary); no golden file yet | | Prepaid expense + deferred revenue amortization | Amortization schedules (Xero/QBO add-ons) | partial | hand-verified fixture, to the cent; no golden file yet | | Automated cross-implementation diff harness (GnuCash first) | — (our `xtask oracle`) | todo | roadmap phase 3 | +| Invoice sent tracking (who, how, to whom, when, original or reminder, the channel's reference) | Business → Invoices (Xero's "Sent"; QBO's email) | done | P4b S5; records, never sends (A-4); a send changes no figure; `invoice.sent` under `documents_v2` (D-52) | Workflow/UI parity (QuickBooks/Xero reference) gets its own catalog when the egui phase starts; semantics come first. @@ -104,3 +105,14 @@ door on loopback for Caddy or a tunnel. `crates/ledger-cli/tests/two_user.rs` proves the account boundary and the move's head in CI (`.github/workflows/two-user.yml`); the recipe is `deploy/README.md`. The cutover rule in `docs/capability-parity.md` is unchanged. + +### P4b S5: sent tracking (2026-10-27) + +The catalog gains one row: an invoice's recorded sends. The books never +send (A-4): `books.mark_sent` records who sent an invoice, how, to whom, +when, whether it was the original or a reminder, and the channel's own +reference, for an owner or a delegate such as an agent. The record is +`invoice.sent`, under `documents_v2` (D-52), and it moves no figure, so +the A/R and A/P rows' figures are unchanged. The floor never decreases: +the row is `done`, tested by `sends_replay_and_project`, +`a_send_changes_no_figure` and `mark_sent_records_who_and_how`. diff --git a/docs/receipt-store.md b/docs/receipt-store.md index bde7404..f6b28a3 100644 --- a/docs/receipt-store.md +++ b/docs/receipt-store.md @@ -58,8 +58,10 @@ apply is safe — on startup the applier replays every journal line with - `ts`: UTC, RFC 3339. Recorded, never trusted for ordering (`seq` orders). - `type`: `account.added`, `entry.posted`, `entry.reversed`, `invoice.created`, `bill.created`, `receipt.attached`, `receipt.linked`, `receipt.unlinked`, - `document.filed`, `attachment.withdrawn` (plus the A/R, A/P, asset, - period, payroll, and class events listed on `JournalLine`). + `document.filed`, `attachment.withdrawn`, `invoice.sent` (a recorded + send: a document's record, never a ledger line or a figure; P4b S5) + (plus the A/R, A/P, asset, period, payroll, and class events listed on + `JournalLine`). - `payload`: the full domain facts (account fields, entry with lines, blob sha, ...). Everything the applier needs; nothing it must look up elsewhere. An `invoice.created` or `bill.created` written after the `documents_v2` diff --git a/docs/spec/rules.md b/docs/spec/rules.md index 161e093..2acfcdb 100644 --- a/docs/spec/rules.md +++ b/docs/spec/rules.md @@ -1744,8 +1744,11 @@ which an earlier version refuses (S4.2); a `party.updated` with an email or a default terms fact (S4.2); an `invoice.created` or `bill.created` carrying a number, terms, a bill-to or seller snapshot, a customer reference or memo, a vendor reference, a receipt date or a duplicate's -reason, or a line naming its account, quantity or unit price (S4.3); and -S5 to S7's sends, settlements and write-offs. An older version would +reason, or a line naming its account, quantity or unit price (S4.3); +S5's `invoice.sent` (who sent an invoice, how, to whom and when: a record +that moves no figure); S6's payments with `line`, `discount`, +`discount_line`, `discount_reason`, `fee` or `fee_line`; and S7's +`invoice.written_off`. An older version would restore the document without them silently: every invoice line would post to the revenue role and every bill line to general expenses, and every new event type would be refused. A command asks for diff --git a/docs/testing.md b/docs/testing.md index c1f814b..53f302d 100644 --- a/docs/testing.md +++ b/docs/testing.md @@ -3050,3 +3050,54 @@ and `BooksServer::new`'s copy (a dropped field must fail `isolation_label_is_enforced_under_the_unit`); the host's `server_flags` extension and `parse_args`' passthrough of `--isolation` and its optional `--http` value. + +## P4b S5: sent tracking (2026-10-27) + +The books record that an invoice went out; they never send it (A-4). A +send is `invoice.sent`, under `documents_v2` (D-52), and moves no figure: +`Ledger::mark_sent` pushes a `Sent` onto the invoice, the store journals +one line and no entry, replay restores it and the projection keeps it in +`schema` v17's `invoice_sends` (S4.3 took 16). + +- **`ledger-store` `tests/arap_standing.rs`:** `mark_sent_waits_for_documents_v2` + (books with no features: refused, nothing journaled; after `upgrade`, + taken, and the reopen equals what was live), `sends_replay_and_project` + (two sends: the journal's `invoice.sent` carries the whole record in + order, `verify` passes, the reopened invoice holds both), + `a_send_changes_no_figure` (on the `p4-arap-before` fixture: the report + snapshot, the invoice's `DocState` and every derived cache are equal + before and after), `mark_sent_on_void_refused` (`DocumentVoid`, + nothing journaled; a send dated before the void is taken). +- **`ledger-store` `tests/arap_props.rs`:** `status_is_a_function_of_journal_and_date` + gains `Op::Sent` (random sends on invoices, skipped on bills): every + figure, the control check and both agings still agree across live, + replay and snapshot. +- **`ledger-store` `tests/format.rs`:** `an_older_version_stops_at_invoice_sent` + (`invoice.sent` is this version's, a reader without `documents_v2` waits, + and the books declare the feature before the first send line). +- **8.0.6:** `books_written_under_older_rules_still_open` hand-writes an + `invoice.sent` and still opens; the fixture `old-books/p4_s5_before` + was written by eeee9658 (the revision before this unit) and opens to + the same figures; `books_p2_served_cut_over_whole` refuses a send before + consent and takes it after. +- **`ledger-cli` `tests/documents.rs`:** `mark_sent_records_who_and_how` + (two sends over the wire: the token's name, email and original by + default, then a reminder with a message id; `books.documents` shows + `last_sent`, `books.document` both sends, and the invoice still owes its + total). +- **`ledger-mcp/src/handbook.rs`:** the new `invoices-and-bills` section + renders, and its id is unique (the existing handbook tests). + +**For P12 (mutation targets):** `Ledger::mark_sent`'s refusals (unknown +invoice, a send dated before it, `DocumentVoid`, empty `to`, and each of +the three `check_text` calls; the note's check is stricter than the plan's +list, to keep a full TIN out of the journal); `SentVia::parse`'s arms and +its `other:` prefix; `SendKind::parse`'s two arms; `sent_via`'s arms and +its empty fallback; `send_kind`'s error arm; the handler's `kind` default +(`original` when `sends` is empty, `reminder` after), its `to` fallback +(bill-to, then party, then refused) and its `by` fallback; `invoice_id`'s +`invoice:` strip; `last_sent`'s `max`; and the replay reader's +`newer_content` arms for an unknown channel or kind. The two attention +predicates ("invoice never sent", "overdue invoice not reminded") and +their day comparisons are deferred with the unit's step 4 until U0b +lands, and are named here when they do.