feat(core,mcp,gui): never apply a legal rule past what wanderbooks knows
The owner (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:
1 parent
8559bba6bc
commit
4eaac20311
15 files changed
+349
-47
No files matched your search
@@ -1,7 +1,7 @@
|
||||
//! What the books need: drafts left unposted, missing receipts, schedules
|
||||
//! and recurring entries due, open past periods, uncategorized tax lines,
|
||||
//! 1099 vendors without a W-9, overdue bills and invoices, money owed to
|
||||
//! owners. Each item says what's wrong, where to look, and the prompt that
|
||||
//! owners, and legal rules (like the 1099 threshold) due for an update. Each item says what's wrong, where to look, and the prompt that
|
||||
//! asks the agent to fix it (the GUI never changes the books itself).
|
||||
|
||||
use crate::books::{Attention, Books, Severity};
|
||||
@@ -176,12 +176,34 @@ pub fn find(b: &Books) -> Vec<Attention> {
|
||||
}
|
||||
}
|
||||
|
||||
// Rules set by law that wanderbooks may no longer know (rules.md;
|
||||
// owner, 2026-10-13): research or ask, then update them for everyone.
|
||||
for rule in ledger_core::rules::stale_rules(today) {
|
||||
out.push(Attention {
|
||||
severity: Severity::Todo,
|
||||
title: format!("Check the {}", rule.name),
|
||||
detail: format!(
|
||||
"wanderbooks knows it through {}. {}",
|
||||
rule.known_through, rule.review_why
|
||||
),
|
||||
target: None,
|
||||
prompt: format!(
|
||||
"wanderbooks' {} is known only through {}. Find the current figure from the official source (see {} in the wanderbooks repo), or ask me or our CPA, and tell me what you found. Then update the rule for everyone: open a pull request at https://github.com/swaits/wanderbooks with the new value and its official source (or at least an issue).",
|
||||
rule.name, rule.known_through, rule.source
|
||||
),
|
||||
});
|
||||
}
|
||||
|
||||
let mut years = vec![today.year()];
|
||||
if today.month() == 1 {
|
||||
years.insert(0, today.year() - 1);
|
||||
}
|
||||
for year in years {
|
||||
let r = vendor_1099_report(&b.ledger, year, None);
|
||||
// A year whose threshold isn't known is flagged by the stale-rule
|
||||
// item above; never guess it here.
|
||||
let Ok(r) = vendor_1099_report(&b.ledger, year, None) else {
|
||||
continue;
|
||||
};
|
||||
for v in r.vendors.iter().filter(|v| v.reportable && !v.tin_on_file) {
|
||||
out.push(Attention {
|
||||
severity: Severity::Problem,
|
||||
|
||||
@@ -413,7 +413,7 @@ fn recurring(ui: &mut Ui, b: &Books) {
|
||||
fn parties(ui: &mut Ui, b: &Books) {
|
||||
let p = pal(ui.ctx());
|
||||
let year = chrono::Datelike::year(&b.today);
|
||||
let r1099 = vendor_1099_report(&b.ledger, year, None);
|
||||
let r1099 = vendor_1099_report(&b.ledger, year, None).ok();
|
||||
columns(ui, 980.0, &[1.0, 1.0], |i, ui| match i {
|
||||
0 => {
|
||||
widgets::titled_card(
|
||||
@@ -427,8 +427,8 @@ fn parties(ui: &mut Ui, b: &Books) {
|
||||
}
|
||||
for v in vendors {
|
||||
let paid = r1099
|
||||
.vendors
|
||||
.iter()
|
||||
.flat_map(|r| &r.vendors)
|
||||
.find(|x| x.vendor_id == v.id)
|
||||
.map(|x| x.paid)
|
||||
.unwrap_or_default();
|
||||
|
||||
@@ -1777,7 +1777,15 @@ fn gl(ui: &mut Ui, b: &Books, s: NaiveDate, e: NaiveDate, shell: &mut Shell<'_>)
|
||||
|
||||
fn vendor_1099(ui: &mut Ui, b: &Books, year: i32, shell: &mut Shell<'_>) {
|
||||
let p = pal(ui.ctx());
|
||||
let r = vendor_1099_report(&b.ledger, year, None);
|
||||
let r = match vendor_1099_report(&b.ledger, year, None) {
|
||||
Ok(r) => r,
|
||||
Err(e) => {
|
||||
// Never guess a threshold (rules.md B-19): say why there's no
|
||||
// worksheet, in the engine's words.
|
||||
ui.label(RichText::new(e.to_string()).color(p.muted));
|
||||
return;
|
||||
}
|
||||
};
|
||||
ui.label(
|
||||
RichText::new(format!(
|
||||
"Calendar {year}. Threshold {} ($600 through 2025; $2,000 from 2026, indexed after). Counts cash paid on entries tagged with the vendor; card payments are reported by the card processor (1099-K) and drop out.",
|
||||
|
||||
Reference in new issue
Block a user