feat(core,mcp,gui): never apply a legal rule past what wanderbooks knows

Stephen (2026-10-13): when what wanderbooks knows is stale, check the
date, research it or ask the owner or CPA, and update the rule for
everyone with a pull request (best) or an issue (worst).

- ledger_core::rules: a registry of rules set by law that change over
  time. Each says the years it's known for, its rules.md entry, its
  vendored official source, and a review date. stale_rules(today) is
  pure; the engine has no clock.
- The 1099 threshold is the first: $600 through 2025, $2,000 in 2026,
  then indexed for inflation (26 U.S.C. §6041(h)). default_threshold
  and vendor_1099_report now return Result; for 2027+ they refuse with
  LedgerError::RuleNotKnown, whose message says what to do: get the
  figure from the official source or the owner/CPA, pass it explicitly,
  then open a PR (or issue) at github.com/swaits/wanderbooks. Before,
  2027+ silently used $2,000.
- MCP: books.vendor_1099 relays that refusal; books.digest gains
  stale_rules; the server instructions tell the agent the procedure (and
  how to handle control-account conflicts: explain, never renumber).
  The CPA package writes a NOT-INCLUDED note instead of a guessed 1099
  worksheet.
- Viewer: "Needs attention" shows each stale rule with a ready prompt
  for the agent; the 1099 page explains instead of guessing.

Review date for the 1099 rule: 2026-11-01, when the IRS usually
publishes next year's inflation adjustments (CONVENTION, marked as such
in the registry).

Tests: an_unknown_year_threshold_is_never_guessed,
dated_rules_go_stale_on_their_review_date (dates passed in, so no clock
in the test), MCP stdio: 2027 refused with guidance, explicit threshold
accepted, digest carries stale_rules (asserted date-independently).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GWVo7x3UHw5kwCUaWxiejX
This commit is contained in:
swaitsandClaude Opus 5.5 committed 2026-10-13 14:07:22 -06:00
1 parent e2bebce051
commit df38dcccf9
15 files changed
+349 -47

No files matched your search

+5 -2
View File
@@ -655,7 +655,7 @@ fn vendor_1099_tags_replay_and_project() {
store.post_entry(dup).unwrap();
store.set_entry_vendor(dup, Some(dana)).unwrap();
store.reverse_entry(dup, d(6, 3), "duplicate").unwrap();
let before = vendor_1099_report(store.ledger(), 2026, None);
let before = vendor_1099_report(store.ledger(), 2026, None).unwrap();
assert_eq!(before.vendors[0].paid, Decimal::from(2200));
assert!(before.vendors[0].reportable);
drop(store);
@@ -666,7 +666,10 @@ fn vendor_1099_tags_replay_and_project() {
std::fs::remove_file(dir.join(ledger_store::DB_FILE)).unwrap();
}
let store = Store::open(&dir).unwrap();
assert_eq!(vendor_1099_report(store.ledger(), 2026, None), before);
assert_eq!(
vendor_1099_report(store.ledger(), 2026, None).unwrap(),
before
);
let conn = rusqlite::Connection::open(dir.join(ledger_store::DB_FILE)).unwrap();
let tagged: i64 = conn
.query_row(