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
@@ -0,0 +1,79 @@
|
||||
//! Rules set by law that change over time, and how far wanderbooks knows
|
||||
//! them.
|
||||
//!
|
||||
//! The engine has no clock, so it never decides on its own that a rule is
|
||||
//! out of date. Instead each dated rule says which years it is known for
|
||||
//! and when it is due for review; callers that know today's date (the MCP
|
||||
//! server, the viewer) ask [`stale_rules`] and surface what is due.
|
||||
//!
|
||||
//! ```text
|
||||
//! GOAL: never apply a legal amount for a year it isn't known for, and say
|
||||
//! so before anyone needs it.
|
||||
//! SOURCE: DECISION (owner, 2026-10-13): when what wanderbooks knows is
|
||||
//! stale, research it or ask the owner or CPA, and update the rule
|
||||
//! for everyone (a pull request, or at least an issue, on
|
||||
//! github.com/swaits/wanderbooks).
|
||||
//! INVARIANTS:
|
||||
//! 1. NEVER returns a rule's value for a year past `known_through`.
|
||||
//! 2. MUST report a rule as stale from its review date on.
|
||||
//! ```
|
||||
//!
|
||||
//! To update a rule: vendor the new official text beside the old in its
|
||||
//! `source` file (docs/spec/README.md, "Vendoring rules"), extend the
|
||||
//! rule's table and `known_through`, move `review_after` to when the next
|
||||
//! change is expected, and say so in the commit.
|
||||
|
||||
use chrono::NaiveDate;
|
||||
|
||||
/// A rule set by law whose value changes over time.
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
pub struct DatedRule {
|
||||
/// Plain name, e.g. "1099 reporting threshold".
|
||||
pub name: &'static str,
|
||||
/// Its entry in docs/spec/rules.md, e.g. "B-19".
|
||||
pub catalog: &'static str,
|
||||
/// The vendored official text it comes from.
|
||||
pub source: &'static str,
|
||||
/// The last year wanderbooks knows the rule for.
|
||||
pub known_through: i32,
|
||||
/// When to look again, as (year, month, day): the day the next value
|
||||
/// is expected to be published or to take effect.
|
||||
pub review: (i32, u32, u32),
|
||||
/// Why that date, and where the next value will come from.
|
||||
pub review_why: &'static str,
|
||||
}
|
||||
|
||||
impl DatedRule {
|
||||
/// The review date.
|
||||
pub fn review_after(&self) -> NaiveDate {
|
||||
let (y, m, d) = self.review;
|
||||
NaiveDate::from_ymd_opt(y, m, d).expect("rule review dates are valid")
|
||||
}
|
||||
|
||||
/// Whether the rule is due for review on `today`.
|
||||
pub fn is_stale(&self, today: NaiveDate) -> bool {
|
||||
today >= self.review_after()
|
||||
}
|
||||
}
|
||||
|
||||
/// The 1099-NEC / 1099-MISC reporting threshold (rules.md B-19): $600 for
|
||||
/// payments through 2025, $2,000 in 2026, then indexed for inflation.
|
||||
pub const THRESHOLD_1099: DatedRule = DatedRule {
|
||||
name: "1099 reporting threshold",
|
||||
catalog: "B-19",
|
||||
source: "docs/spec/sources/irs-1099-thresholds.md",
|
||||
known_through: 2026,
|
||||
review: (2026, 11, 1),
|
||||
review_why: "26 U.S.C. §6041(h) indexes the threshold for calendar years after 2026, \
|
||||
rounded to the nearest $100; the IRS publishes each year's inflation \
|
||||
adjustments late in the year before (CONVENTION: the usual timing, not a \
|
||||
cited date), so look for the 2027 figure from November 2026.",
|
||||
};
|
||||
|
||||
/// Every dated rule the engine applies.
|
||||
pub const DATED_RULES: &[DatedRule] = &[THRESHOLD_1099];
|
||||
|
||||
/// The dated rules due for review on `today`.
|
||||
pub fn stale_rules(today: NaiveDate) -> Vec<&'static DatedRule> {
|
||||
DATED_RULES.iter().filter(|r| r.is_stale(today)).collect()
|
||||
}
|
||||
Reference in new issue
Block a user