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:
1 parent
7299cf90f2
commit
6a66a2855b
49 files changed
+2526
-202
No files matched your search
@@ -297,3 +297,56 @@ fn an_unsent_invoice_is_on_both_lists() {
|
||||
assert_eq!(step["tool"], json!("books.mark_sent"), "{answer}");
|
||||
assert_eq!(step["args"]["invoice"], json!("invoice:1"), "{answer}");
|
||||
}
|
||||
|
||||
/// Plan P4 step 6 (R-F): a draft that says it waits for its evidence is
|
||||
/// one item, "draft awaiting receipt", and a draft still being built is
|
||||
/// another, "draft not posted": both on the viewer's list and in
|
||||
/// `books.start`'s next steps, from the one engine.
|
||||
#[test]
|
||||
fn a_waiting_draft_and_a_building_draft_are_two_items_on_both_lists() {
|
||||
let today = ledger_core::settings::today_in(None);
|
||||
let place = common::Place::new("attention-drafts-parity");
|
||||
let dir = place.books_data();
|
||||
let _ = std::fs::remove_dir_all(&dir);
|
||||
let mut st = ledger_store::Store::create(&dir, Currency::USD).unwrap();
|
||||
let cash = st
|
||||
.add_account("1000", "Checking", AccountType::Asset, None, false)
|
||||
.unwrap();
|
||||
let rent = st
|
||||
.add_account("6000", "Rent", AccountType::Expense, None, false)
|
||||
.unwrap();
|
||||
let mut draft = |what: &str| {
|
||||
let e = st.create_entry(today - chrono::Days::new(5), what).unwrap();
|
||||
st.add_line(e, rent, Side::Debit, usd(4_000)).unwrap();
|
||||
st.add_line(e, cash, Side::Credit, usd(4_000)).unwrap();
|
||||
e
|
||||
};
|
||||
let waiting = draft("Figma annual plan");
|
||||
let _building = draft("Coworking day pass");
|
||||
st.set_draft_state(
|
||||
waiting,
|
||||
ledger_core::DraftState::AwaitingEvidence {
|
||||
note: "invoice due".into(),
|
||||
},
|
||||
)
|
||||
.unwrap();
|
||||
drop(st);
|
||||
let mut wire = place.start(Some(SETTINGS));
|
||||
wire.initialize("attention-drafts-parity");
|
||||
compare_and_expect(
|
||||
&place,
|
||||
&mut wire,
|
||||
today,
|
||||
&format!("Draft #{waiting} awaiting receipt: Figma annual plan"),
|
||||
);
|
||||
let titles: Vec<String> = start(&mut wire)["next_steps"]
|
||||
.as_array()
|
||||
.unwrap()
|
||||
.iter()
|
||||
.filter_map(|s| s["why"].as_str().map(str::to_string))
|
||||
.collect();
|
||||
assert!(
|
||||
titles.contains(&"Draft not posted: Coworking day pass".to_string()),
|
||||
"{titles:?}"
|
||||
);
|
||||
}
|
||||
Reference in new issue
Block a user