Control roles in the journal (books.assign_role, books.roles); the MCP reloads wanderbooks.toml on change and reports settings problems (books.settings, digest); new writes a full commented wanderbooks.toml
check / check (push) Canceled after 0s

- core: ControlAccount keys, Ledger::assign_role/check_assign_role/role_account (rules.md B-23): one account per role, of the role's type, balance sheet roles move only when empty; invoices, bills, payroll, remittance, and closes post to the assigned account; an assigned role frees its default code for renumbering
- store: role.assigned journaled and replayed (strict account_id)
- mcp: settings.rs reloads by content at the start of each request, keeps the last good settings on a broken edit; books.settings with chart problems; digest settings_problems; instructions say when to edit the toml
- viewer: journal text for role.assigned
- cli: full example wanderbooks.toml, tested to load with every example uncommented
- tests: spec_roles.rs (25, spec-blind), store roles.rs (6), MCP reload/problems/roles, settings unit tests

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-15 10:30:16 -06:00
1 parent efa370dfa3
commit 1771d87e0e
19 files changed
+3044 -132

No files matched your search

+40 -2
View File
@@ -97,8 +97,8 @@ pub enum LedgerError {
/// decides with the human how to handle it.
#[error(
"account {code} \"{name}\" is {}, but {role} needs account {code} to be a postable {expected:?} account; \
wanderbooks won't post {role} there. Decide with the books' owner how to handle it \
(for instance, renumber that account so {code} is free, if they agree)",
wanderbooks won't post {role} there. Decide with the books' owner how to handle it: \
assign {role} to an account of theirs, or renumber that account so {code} is free",
if *placeholder { format!("a placeholder {found:?} account") } else { format!("a {found:?} account") }
)]
ControlAccountMismatch {
@@ -116,6 +116,44 @@ pub enum LedgerError {
placeholder: bool,
},
/// No control role has that name (see `ledger_core::control_accounts`).
#[error(
"no role {0:?}; the roles are receivables, revenue, payables, general_expense, \
retained_earnings, salary_expense, payroll_tax_expense, federal_withholding, \
state_withholding, social_security_payable, medicare_payable, other_deductions_payable"
)]
UnknownRole(String),
/// One account per role: an account already filling a role can't take
/// a second (its balance would mix what the two control).
#[error("account {code} \"{name}\" already holds {role}; give each role its own account")]
RoleTaken {
/// The account's code.
code: String,
/// The account's name.
name: String,
/// The role it fills, e.g. "accounts receivable".
role: String,
},
/// A balance sheet role (receivables, payables, withholdings, retained
/// earnings) can't move while its account holds a balance: the control
/// account must keep agreeing with what it controls.
#[error(
"{role} is in account {code} \"{name}\", which holds {balance}; move that balance to the \
new account with a journal entry first, then assign the role"
)]
RoleHoldsBalance {
/// The role, e.g. "accounts receivable".
role: String,
/// The code of the account filling it now.
code: String,
/// That account's name.
name: String,
/// Its balance.
balance: rust_decimal::Decimal,
},
/// A rule set by law (a dollar threshold, a rate) isn't known to
/// wanderbooks for the year asked; the engine never guesses (see
/// [`crate::rules`]).