feat(drafts): the drafts tools, the preview digest on every confirm, the digest tool (P4)

Accounting reason: a confirm must act on the thing that was previewed.
Every tool with confirmed (post_entry, reverse_entry, run_schedules,
dispose_asset, reconcile, close_period, create_invoice, create_bill,
write_off_invoice, purge_tin, consolidate_duplicates, and the S6
settlement tools receive_payment and pay_bill, which gain confirmed and
preview on a clone of the ledger) returns preview (SHA-256 of the
canonical preview) and refuses a confirm whose preview is missing or
stale (stale_preview, before anything is written). A draft's digest is
its own state and the settings seq; an operation's is the tool, its
arguments as sent, the settings seq and the journal head seq, so another
session's write forces a re-preview (D-50).

New tools books.list_drafts, books.set_draft_state, books.discard_draft,
books.preview_entry; reports carry as_of_status (drafts open on or before
their end, unreconciled accounts, settings problems) and take strict; the
CPA package is refused while a draft dated in it is open unless
allow_open_drafts gives a reason, journaled as a decision;
books.digest takes since_seq (what was posted since a journal line,
backdated entries included: defect 6) and lists zero_net_accounts,
drafts_by_state and the reconciliation split. Needs attention gains
'draft awaiting receipt' beside 'draft not posted'; the drafts handbook
is rewritten. Every over-the-wire harness confirms the way an agent does.

Docs: mcp-interface, rules B-43, parity, testing (with the For-P12
paragraph), architecture, and the unit's As-built block.

Tests: drafts.rs (a_draft_waits_for_its_receipt_and_posts,
the_gate_moved_and_the_mcp_error_is_unchanged,
a_reversal_needs_no_receipt_of_its_own, discard_unblocks_the_close_over_the_wire,
report_warns_on_draft_before_as_of, cpa_package_refuses_open_draft,
stale_preview_refused, every_confirm_tool_takes_a_preview,
digest_since_seq_is_posting_time, legacy_reversed_replays_identically,
gui_and_server_gate_agree), attention_parity.rs, format_upgrade.rs.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HS8WZziDnyxunC23MUxrLp
This commit is contained in:
swaitsandClaude Fable 5.1 committed 2026-10-29 13:55:38 -06:00
1 parent 7299cf90f2
commit 6a66a2855b
49 files changed
+2526 -202

No files matched your search

+75
View File
@@ -3594,3 +3594,78 @@ token" must die on `an_unreadable_token_file_is_an_error_never_no_token`),
`Keeper::delete`'s `NotFound` arm, and `Account::key`'s three parts
(`an_account_names_the_root_by_hash_and_the_book_and_token`);
`bridge.rs::token_file_refusal`'s `holds_token` guard.
## P4: draft states, the evidence gate, the preview digest (2026-10-29)
A draft has a state (`building`, or `awaiting_evidence` with a note) and a
way out (`books.discard_draft`); one function, `evidence::requirement`,
decides whether an entry needs evidence; every tool with `confirmed` takes
the `preview` digest it returned; `books.digest` can be asked "since a
journal line". Journal feature `drafts_v2`; events `entry.draft_state_set`
and `entry.draft_discarded`; schema 19 (taken as the next after the last,
renumbered at merge): `entries.posted_seq` and the `draft_states` table
(drafts are not rows of `entries`, so the plan's `entries.draft_state`
column became a table).
- **`ledger-core` `tests/invariants.rs`:** `discard_unblocks_close` (a
period with an open draft can't close, a state changes no figure,
discarded it closes; balances, order and ids as they were; a posted
entry is never discarded or given a state).
- **Lean:** `Step.discard` (preservation of `Inv`, including
`draftsOpen`), `Step.discard_keeps_books`, `Step.discard_drafts`,
`Step.discard_unblocks_close`, in `Audit.lean`.
- **`ledger-store` `tests/store.rs`:** `draft_states_replay_and_project`
(both events journaled as written, `draft_states` holds only the waiting
draft and loses it when it posts, live equals replay, the refusals),
`drafts_v2_waits_for_the_upgrade`, `posted_seq_is_the_posting_lines_seq`
(hand, backdated, reversal and closing entries: the number is the line
that posted them, live, replayed and projected);
`books_written_under_older_rules_still_open` hand-writes both events.
`tests/format.rs`: `an_older_version_stops_at_the_draft_events`;
`format.rs`'s `drafts_v2_is_its_own_feature`.
- **8.0.6:** the fixture `old-books/p4_before` (writer
`old-books-writer/src/bin/p4_before.rs`, run against 25b52580) holds
hand entries posted late, a reversal, a document and its payment, a
schedule month, a recurring draft, two open drafts and a closed month;
`every_book_an_earlier_version_wrote_upgrades_and_still_verifies` opens
it to the writer's figures, so no figure moved (serialized entries omit
a building state). `books_p2_served_cut_over_whole` refuses the two draft
tools before consent and takes them after.
- **`ledger-cli` `tests/drafts.rs`:** `a_draft_waits_for_its_receipt_and_posts`,
`the_gate_moved_and_the_mcp_error_is_unchanged`,
`a_reversal_needs_no_receipt_of_its_own` (D-47),
`discard_unblocks_the_close_over_the_wire`,
`report_warns_on_draft_before_as_of` (and `strict`),
`cpa_package_refuses_open_draft` (and `allow_open_drafts` journaling a
decision), `stale_preview_refused` (a changed line, a changed argument,
a write between preview and confirm, no preview),
`every_confirm_tool_takes_a_preview`, `digest_since_seq_is_posting_time`
(with `zero_net_accounts`, `drafts_by_state`, `never_reconciled`),
`legacy_reversed_replays_identically` (the fixture over the wire),
`gui_and_server_gate_agree` (three policies over random books; the
viewer's missing-evidence set equals the engine's and equals the old
reading of an entry's origin from the journal's events).
- **`ledger-cli` `tests/attention_parity.rs`:**
`a_waiting_draft_and_a_building_draft_are_two_items_on_both_lists`.
- **The sweep:** every over-the-wire harness confirms the way an agent does
(`common::tools_call`: the call without `confirmed`, then with the
`preview`); `books.receive_payment` and `books.pay_bill` gained
`confirmed`; `ledger-mcp` `scopes.rs` tests pin them as preview-then-post.
**For P12 (mutation targets):** `evidence::requirement`'s exemption arms
(reversal, closing, schedule month, depreciation run, disposal origin,
payroll remittance origin, a tagged document line, a payroll run) and
their order, the split-group branch taken only for a `Draft`, and
`Policy::reason_for`'s three comparisons (required account, `<=` the
threshold, every code exempt); `Ledger::origin_exemption`; the replay
arms that note `AssetDisposal` and `PayrollRemittance`;
`Ledger::discard_draft`'s empty-reason and posted refusals and the side
tables it clears; `set_draft_state`'s `check_text`; `stamp_unstamped` and
`expect_posted_seq` (a mutant that stamps the wrong line shows in
`posted_seq_is_the_posting_lines_seq`); `Preview::operation` (the keys it
drops: `confirmed`, `preview`, nulls) and `Preview::check`'s three arms
(missing, equal, different); `post_entry`'s draft payload (each field of
it); the digest's `since_seq` comparison (`>`), its `as_of` bound, the
`zero_net_accounts` branch and the `never_reconciled` split;
`report_status`'s three open conditions; the open-drafts refusal's
`<= as_of` and its decision; the attention `awaiting_evidence` branch.