docs(testing): the P4b S1-B, S2 and S3 sections (U0c)
The 8.0.4 bar asks every unit to record what P12 must mutate (D-33), and
P4b S1 part A has its paragraph while part B, S2 and S3 shipped without
one. S1-B's targets are the payment details (a reference is one line of
at most 200 characters, carried in the entry's description; the
instrument is tagged in the same call) and the two handlers that put
B-38 on the wire (void, control check). S2's are the status filters and
the timeline labels that make a reversed payment read as reversed
instead of disappearing. S3's are the stamp arms and the re-created-copy
branch, where an invoice that has no kept copy says so.
The S2 section records what shipped against the plan's As-built words:
the timeline labels reversed items ("Payment reversed", "Payment
(reversed later)"); the viewer draws no strikethrough.
This commit is contained in:
1 parent
1bfce9157a
commit
e9febc5694
1 file changed
+160
+160
@@ -2647,3 +2647,163 @@ amount match and one-line rule; `refresh_doc`'s settled sum;
|
|||||||
`doc_status`'s Overpaid and Unattached arms; the aging's `<= 0`;
|
`doc_status`'s Overpaid and Unattached arms; the aging's `<= 0`;
|
||||||
`ensure_payment_features`' comparison; `check_document_reversal`'s date
|
`ensure_payment_features`' comparison; `check_document_reversal`'s date
|
||||||
check; `control_check`'s chain netting.
|
check; `control_check`'s chain netting.
|
||||||
|
|
||||||
|
## P4b S1, part B: void, control check, and each payment's details (2026-10-24)
|
||||||
|
|
||||||
|
The MCP layer of rules.md B-38 (plan P4b S1, part B). Part A (above) is
|
||||||
|
the engine: the tags, the derived figures, the guards. Part B puts it on
|
||||||
|
the wire: `books.void_document` reverses a document's own entry with the
|
||||||
|
reason, `books.control_check` ties each control account to its documents,
|
||||||
|
and `books.receive_payment` and `books.pay_bill` take `instrument` and
|
||||||
|
`reference` and answer the document's derived `remaining` and `status`.
|
||||||
|
It adds no engine rule, kernel function, Kani harness or Lean theorem:
|
||||||
|
it calls what part A proved and tested.
|
||||||
|
|
||||||
|
- **Over the wire, `crates/ledger-cli/tests/spec_mcp.rs`:**
|
||||||
|
`documents_void_and_tie_out_over_the_wire`. A payment of 200.00 against
|
||||||
|
a 500.00 invoice, recorded with instrument "Harbor checking" and
|
||||||
|
reference "tr_8812", answers `remaining` 300.00 and `status` partial,
|
||||||
|
and its entry carries both (the instrument, and a description ending
|
||||||
|
"(ref tr_8812)"). Voiding that invoice is refused and the refusal names
|
||||||
|
the payment entry; an invoice entered twice is voided and reads
|
||||||
|
"void"; a hand line to 1200 is refused. `books.control_check` at
|
||||||
|
2026-06-30 agrees: receivables 1200 has balance 300.00,
|
||||||
|
`documents_total` 300.00 and `difference` 0.00; `books.digest` has no
|
||||||
|
`control_problems` and `books.verify` names the control check it made.
|
||||||
|
The happy-path journey (`happy_path`) voids an invoice and runs the
|
||||||
|
check, so both output schemas are exercised on every run.
|
||||||
|
- **The tool surface, `crates/ledger-mcp/src/server.rs`'s tests:**
|
||||||
|
`annotations_complete` pins `books.void_document` destructive and
|
||||||
|
posting, `books.control_check` read-only, and counts the 29
|
||||||
|
destructive tools the doc names; `period_tools_describe_the_one_default`
|
||||||
|
holds `books.control_check` to the one `as_of` sentence. The generic
|
||||||
|
lints cover the rest without a change of their own:
|
||||||
|
`every_tool_is_documented_and_every_documented_tool_exists`,
|
||||||
|
`tool_text_lint`, `tool_descriptions_fit_700_characters`. `scopes.rs`'s
|
||||||
|
table and `crates/ledger-cli/tests/mcp_journeys.rs::mcp_end_to_end`
|
||||||
|
both count every tool (120 when part B landed, 122 with S2's two).
|
||||||
|
- **Handbook:** `revenue-and-payouts` says what a payment carries, that a
|
||||||
|
document that should never have existed is voided and never deleted,
|
||||||
|
and that `books.control_check`'s findings go to the human.
|
||||||
|
|
||||||
|
**For P12 (mutation targets):** `PaymentDetails::check` (the newline test,
|
||||||
|
the 200-character bound, a blank reference as none);
|
||||||
|
`PaymentDetails::description` (the " (ref R)" suffix, the trim, a blank
|
||||||
|
reference leaving the base); the server's `payment_details` (id or label resolution, an
|
||||||
|
unknown instrument refused); `payment_out` (as of the end of the books,
|
||||||
|
`remaining` from outstanding, `status` from `key()`); `doc_ref` (trim and
|
||||||
|
case, the two kinds, anything else refused); the `void_document` handler
|
||||||
|
(the status it answers, `map_or("void", ..)` after the day); `control_check`
|
||||||
|
and `control_check_out` (`agrees` as zero difference and no unattached
|
||||||
|
line and nothing needing attention, each field's mapping, the
|
||||||
|
invoice/bill kind); `control_problems` (one message per unattached line
|
||||||
|
and per document, both loops); the `digest` that includes it and the
|
||||||
|
`verify` check that refuses a nonzero difference.
|
||||||
|
|
||||||
|
## P4b S2: reading documents, in the tools and in the viewer (2026-10-24)
|
||||||
|
|
||||||
|
`books.documents` and `books.document` (`7d2bf0b7`) and the viewer's
|
||||||
|
Money in and Money out lists and a document's page (`b7b7f171`). No
|
||||||
|
format change and no engine rule: both read the figures part A derives
|
||||||
|
(B-38), and the viewer reads the same ledger directly (I-8).
|
||||||
|
|
||||||
|
The plan calls the timeline's reversed items "struck through"; as built
|
||||||
|
they are labelled: a payment reversed later reads "Payment (reversed
|
||||||
|
later)" and its reversal "Payment reversed". The viewer draws no
|
||||||
|
strikethrough, and the test asserts the labels.
|
||||||
|
|
||||||
|
- **Over the wire, `crates/ledger-cli/tests/spec_mcp.rs`:**
|
||||||
|
`documents_list_and_timeline_over_the_wire`. Two invoices (500.00 due
|
||||||
|
Mar 1, 80.00 due Apr 30), a 200.00 payment against the first, and that
|
||||||
|
payment reversed. `books.documents` at 2026-03-15 lists both as open,
|
||||||
|
due date first, `outstanding_total` 580.00, the first 14 days overdue;
|
||||||
|
`status` overdue keeps one, `due_by` 2026-03-31 keeps one, and the
|
||||||
|
bills list is empty. `books.document` as of that day owes 500.00, paid
|
||||||
|
0.00, status open, and its timeline is three moves in posting order:
|
||||||
|
the origin standing, the payment not standing, and its reversal
|
||||||
|
standing. A bad ref ("invoice") and a bad status ("late") are refused.
|
||||||
|
The happy-path journey lists and reads one, so both output schemas are
|
||||||
|
exercised on every run.
|
||||||
|
- **The tool surface:** `annotations_complete` pins both read-only
|
||||||
|
(table, now 122 tools); `period_tools_describe_the_one_default` holds
|
||||||
|
both to the `as_of` sentence;
|
||||||
|
`every_tool_is_documented_and_every_documented_tool_exists` and the
|
||||||
|
text lints cover the doc; `scopes.rs`'s table and
|
||||||
|
`mcp_journeys.rs::mcp_end_to_end` count 122.
|
||||||
|
- **The viewer, `crates/ledger-gui/tests/invoices.rs`:**
|
||||||
|
`the_list_shows_the_ledgers_figures` (the reversed payment no longer
|
||||||
|
counts: $580 owed, $500 of it late; "Invoice 1, Harbor Client, 14 days
|
||||||
|
late, owes 500.00"; the paid invoice under All; the list ties to 1200;
|
||||||
|
the payables list); `a_documents_page_shows_its_timeline` (the payment
|
||||||
|
"(reversed later)", its reversal "Payment reversed", received $0.00,
|
||||||
|
and a row opens its entry); `the_overview_shows_what_is_owed_both_ways`
|
||||||
|
("Owed to you", "You owe on bills", "1 invoice overdue");
|
||||||
|
`books_without_documents_show_no_overview_tiles_for_them` (no tiles, no
|
||||||
|
list). Each page is drawn for `SHOT_DIR`.
|
||||||
|
|
||||||
|
**For P12 (mutation targets):** the `documents` handler's status arms
|
||||||
|
(`"all"`; `"open"` as `!st.outstanding.is_zero() && key != "void"`;
|
||||||
|
`"overdue"` as `st.overdue_days > 0`; `key == other`), the party compare,
|
||||||
|
`due_by`'s `>` (due on or before the day), the `(due, id)` sort and the
|
||||||
|
`outstanding_total` sum; the `document` handler's
|
||||||
|
`l.date <= as_of` timeline filter,
|
||||||
|
`stands`/`reversal` from `stands_on`, `movement_key`'s arms, and
|
||||||
|
`parse_doc_ref`'s split, parse and kind; `DocStatus::key`'s arms, which
|
||||||
|
four of the filters compare against; the viewer's
|
||||||
|
`Filter::keeps` arms (Open's `!outstanding.is_zero() && != Void`,
|
||||||
|
Overdue's `overdue_days > 0`, Paid, Void, All), a document after the day
|
||||||
|
shown only under All (`Ok(None)`), the list's `(due, date, name)` sort
|
||||||
|
and the timeline's movement×reversal labels with their `(reversed later)`
|
||||||
|
condition (`!t.reversal && !stands_on(t.entry, b.today)`).
|
||||||
|
|
||||||
|
## P4b S3: the invoice PDF (2026-10-24)
|
||||||
|
|
||||||
|
An invoice as a PDF: `ledger_import::invoice_pdf::invoice_pdf` typesets
|
||||||
|
`templates/invoice.typ` with the bundled fonts, and
|
||||||
|
`ledger_package::invoice::invoice_document` builds what it prints from the
|
||||||
|
books once, so the server's download and the viewer's Save give the same
|
||||||
|
bytes for the same books. `books.document { pdf: true }` hands the file
|
||||||
|
over; a bill's PDF is the vendor's, so it is refused. No format change
|
||||||
|
and no engine rule: the figures are part A's (B-38) and the template does
|
||||||
|
no arithmetic.
|
||||||
|
|
||||||
|
- **`crates/ledger-import/src/invoice_pdf.rs`:**
|
||||||
|
`an_invoice_renders_deterministically` (the same document renders to
|
||||||
|
the same `%PDF` bytes twice; saved under `SHOT_DIR` to look at);
|
||||||
|
`text_is_data_and_money_is_formatted_here` (the total, the balance and
|
||||||
|
a settled line as printed, and a description that is Typst markup
|
||||||
|
printed literally); `stamps_render_and_no_lines_is_refused` (every
|
||||||
|
`InvoiceStamp` renders, and an invoice with no lines is refused).
|
||||||
|
- **`crates/ledger-package/src/invoice.rs`:**
|
||||||
|
`an_invoice_prints_its_figures_as_of_a_day` (before and after the
|
||||||
|
payment: the balance, the one settled line, PAID with the day, the
|
||||||
|
seller's address lines, the bill-to, the printed issue date, and the
|
||||||
|
re-created note's day);
|
||||||
|
`the_sample_invoice_in_each_state` (through the books: part paid and
|
||||||
|
past due, its settled line reading "Payment Oct 6, 2026 · ACH · ref
|
||||||
|
tr_8812"; paid in full with PAID and its day; void; each saved under
|
||||||
|
`SHOT_DIR`).
|
||||||
|
- **Over the wire:** `spec_mcp.rs::documents_list_and_timeline_over_the_wire`
|
||||||
|
grew the PDF block: `pdf: true` answers a handed file whose download
|
||||||
|
starts `%PDF` and is named `invoice-<id>.pdf`, and `pdf: true` on a
|
||||||
|
bill is refused.
|
||||||
|
- **The viewer:**
|
||||||
|
`crates/ledger-gui/tests/invoices.rs::a_documents_page_shows_its_timeline`
|
||||||
|
also asserts an invoice's page offers "Save invoice PDF" (a bill's page
|
||||||
|
offers none: the file filed with it is the vendor's copy).
|
||||||
|
|
||||||
|
**For P12 (mutation targets):** `InvoiceStamp::text`'s five arms and
|
||||||
|
`template_data`'s `stamp_draft` test (`== Draft`); `invoice_document`'s
|
||||||
|
stamp match (Void, then Paid with `last_paid`, then `overdue_days > 0`
|
||||||
|
before PartiallyPaid, then none); the settled loop (`movement ==
|
||||||
|
Payment`, `!reversal`, `l.date > as_of`, `!stands_on`, the negated
|
||||||
|
amount, the method and the " · ref " suffix parsed back out of the
|
||||||
|
description, and `last_paid`); the re-created-copy (legacy) branch:
|
||||||
|
`number: format!("Invoice {id}")`, `terms: None`, and the `note` that
|
||||||
|
names `today`, which S4 replaces when the issued copy is kept; `dollars`'
|
||||||
|
sign handling and `format_amount`'s parenthesized negatives, which the
|
||||||
|
settled lines print; `template_data`'s no-lines refusal. Two arms no test
|
||||||
|
asserts from `invoice_document` today, so expect survivors there until
|
||||||
|
one does: `PartiallyPaid` (no sample is part paid and not yet due) and
|
||||||
|
`Draft` (only S4's preview reaches it; the renderer's own test renders
|
||||||
|
it but asserts only that it succeeds).
|
||||||
Reference in new issue
Block a user