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

+33 -3
View File
@@ -43,6 +43,35 @@ advanced, such as a "Due to owners" liability: ASC 230-10-45-14(b)), and
230-10-20); the tools and the viewer both read them, and a call may pass
its own (docs/spec/rules.md B-14).
The server reloads `wanderbooks.toml` (and `config.json`'s receipt policy)
whenever either changes, compared by content at the start of each request,
so an edit takes effect on the next call with no restart. A broken edit
keeps the last good settings in force and is reported until fixed; the
data dir and base currency are fixed at startup (`ledger-mcp/src/settings.rs`).
- `books.settings {}` → `{ books_toml, books_toml_exists, config_json,
reload_error, loads, company, fiscal_year_start, tax_form, basis,
receipt_rule, members, cash_accounts, financing_accounts,
investing_accounts, close_into, close_sweep, problems }`: the settings in
force and where they come from. `problems` lists settings that disagree
with the chart (a code not in it, or of the wrong kind), each saying what
to change; `books.digest` repeats them, with a failed reload, as
`settings_problems`. The agent edits the file when the human gives a
company fact, a refusal names a setting, or a problem is listed, then
checks `books.settings`.
The accounts invoices, bills, payroll, and closes post to are not settings:
they are control roles in the journal (rules.md B-23).
- `books.roles {}` → `{ roles: [{ role, purpose, account_type,
default_code, assigned, account_code?, account_name? }] }`: every control
role and the account filling it.
- `books.assign_role { role, account? }` → `{ roles }`: assign a role one
of the chart's accounts (`account` null: back to the default code).
Refuses the wrong type, a placeholder, an account already filling
another role, and moving a balance sheet role while its account holds a
balance. Journaled as `role.assigned`.
## Transports
1. **stdio** (build first). For Claude Code, Claude Desktop, local agents.
@@ -344,9 +373,10 @@ posting.
- `books.renumber_account { account, code }` → `{ id, previous_code,
code, config_mentions }`: give an account a new code. Only the code
changes (entries name accounts by id), so balances and reports don't
move. Refuses a code in use, or a control account's code for an account
of the wrong type. `config_mentions` lists the wanderbooks.toml settings
still naming the old code. Journaled as `account.renumbered`.
move. Refuses a code in use, or an unassigned control role's default
code for an account of the wrong type. `config_mentions` lists the
wanderbooks.toml settings still naming the old code (edit them; the
server reloads the file). Journaled as `account.renumbered`.
- `books.set_tax_category { account, category?, deductible_percent? }`:
map a revenue or expense account to a category (null clears),
optionally overriding its deductible percent. Journaled as
+26
View File
@@ -420,3 +420,29 @@ taxable years beginning on or after January 1, 2016. The 12-month rule:
§1.263(a)-4(f)(1). The election is made yearly on the return.
**Enforced:** bookkeeper policy (books' AGENTS.md), not the engine.
## B-23 Control roles: one account each, of the right kind, moved only when empty
**Status:** CONVENTION (a control account agrees with the records it
controls); DECISIONS (Stephen, 2026-10-13 and 2026-10-15)
Invoices, bills, payroll, and closes post to accounts the engine picks by
role: receivables, revenue, payables, general expense, retained earnings,
salary and payroll tax expense, and five payroll withholdings. A role uses
its default code (1200, 4000, 2000, 6999, 3999, 6100, 6110, 2110–2150)
until the human assigns it one of the chart's own accounts; the assignment
is journaled, so replay never depends on a setting outside the journal
(DECISION, 2026-10-15). A role's account is postable and of the role's
type (B-4), and the engine refuses one that isn't rather than renumber
the chart (DECISION, 2026-10-13). No account fills two roles. A
receivables, payables, withholding, or retained earnings account must keep
agreeing with what it controls (open invoices, open bills, taxes withheld,
closed earnings): such a role moves only when its account holds no
balance, so the bookkeeper first moves the balance with an entry, which
the journal shows. Revenue and expense roles move at any time; earlier
postings stay where they are.
**Enforced:** `Ledger::check_controls`, `Ledger::assign_role` /
`check_assign_role`, `Ledger::check_renumber_account` (an assigned role
frees its code). **Checked:** `ledger-core` `tests/spec_roles.rs`;
`ledger-store` replay of `role.assigned`.