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.
This commit is contained in:
swaits committed 2026-10-27 13:24:10 -06:00
1 parent 47c0e4396e
commit 7c5a2fe3ce
4 files changed
+72 -4

No files matched your search

+12
View File
@@ -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 | | 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 | | 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 | | 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 Workflow/UI parity (QuickBooks/Xero reference) gets its own catalog when the
egui phase starts; semantics come first. 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 proves the account boundary and the move's head in CI
(`.github/workflows/two-user.yml`); the recipe is `deploy/README.md`. (`.github/workflows/two-user.yml`); the recipe is `deploy/README.md`.
The cutover rule in `docs/capability-parity.md` is unchanged. 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`.
+4 -2
View File
@@ -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). - `ts`: UTC, RFC 3339. Recorded, never trusted for ordering (`seq` orders).
- `type`: `account.added`, `entry.posted`, `entry.reversed`, `invoice.created`, - `type`: `account.added`, `entry.posted`, `entry.reversed`, `invoice.created`,
`bill.created`, `receipt.attached`, `receipt.linked`, `receipt.unlinked`, `bill.created`, `receipt.attached`, `receipt.linked`, `receipt.unlinked`,
`document.filed`, `attachment.withdrawn` (plus the A/R, A/P, asset, `document.filed`, `attachment.withdrawn`, `invoice.sent` (a recorded
period, payroll, and class events listed on `JournalLine`). 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 - `payload`: the full domain facts (account fields, entry with lines, blob
sha, ...). Everything the applier needs; nothing it must look up elsewhere. sha, ...). Everything the applier needs; nothing it must look up elsewhere.
An `invoice.created` or `bill.created` written after the `documents_v2` An `invoice.created` or `bill.created` written after the `documents_v2`
+5 -2
View File
@@ -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` 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 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 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 reason, or a line naming its account, quantity or unit price (S4.3);
S5 to S7's sends, settlements and write-offs. An older version would 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 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 to the revenue role and every bill line to general expenses, and every new
event type would be refused. A command asks for event type would be refused. A command asks for
+51
View File
@@ -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` `isolation_label_is_enforced_under_the_unit`); the host's `server_flags`
extension and `parse_args`' passthrough of `--isolation` and its optional extension and `parse_args`' passthrough of `--isolation` and its optional
`--http` value. `--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.