Commit Graph
91 Commits
Author SHA1 Message Date
swaitsandClaude Fable 5.1 8f2e3378e8 feat(viewer): the agent dock and the first-run page (P11b)
D-27: a terminal panel along the bottom of the viewer (alacritty_terminal over
a PTY) runs Claude Code (default) or Codex in the book's books home, resizable,
collapsible, the choice remembered. The agent is an ordinary child process the
owner describes (agent_start); the viewer stays read-only: the agent writes
through the server like any other. A missing agent is offered through its
vendor's installer (a command to copy), never bundled; no folder is marked
trusted (Q-7, recommended answer: let Codex ask). Windows is not offered until
its ConPTY checks are recorded.

P11a found that onboarding with no terminal was false: the viewer opened only an
existing book. With no books and an owner, wanderbooks gui now shows a first-run
page: the private folder, the owner passphrase (typed in the viewer, optional),
the first books, an agent, then the books open. connect and New books... make
the books home. Tests: dock.rs, welcome.rs, books_home.rs (host and cli).
Docs: testing.md record and For P12, architecture, parity, gui.md, README.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HS8WZziDnyxunC23MUxrLp
2026-10-29 17:06:14 -06:00
swaitsandClaude Fable 5.1 b77a25fd7b refactor(bookspec): the specification lives in its own repository (P13 step 6)
bookspec/ (spec, examples, checker, runner, Lean, GnuCash adapter) moves to
https://git.swaits.com/swaits/bookspec, history kept by git subtree split
(public home; CC BY 4.0 for spec/ and examples/, MIT OR Apache-2.0 for the
code). ledger-core and ledger-package depend on bookspec-checker and
bookspec-runner as git dev-dependencies pinned to
74f2853d5f0db0066147c17fed8033cfe3d83a24, and read the examples through
bookspec_checker::examples(). just check still runs every example via
cargo test --workspace. The pin check against docs/spec/sources and the
rules.md id check move into tests/bookspec.rs (they need this repository's
files). The accounting is unchanged: no example or rule moved.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HS8WZziDnyxunC23MUxrLp
2026-10-29 16:51:18 -06:00
swaitsandClaude Fable 5.1 6f948592f6 merge: P11a, the AppImage restart, the daily staged update, the unsigned macOS bundle in CI (branch P11a, 310fa105)
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HS8WZziDnyxunC23MUxrLp
2026-10-29 16:29:21 -06:00
swaitsandClaude Fable 5.1 bc9aa57380 feat(packaging): the unsigned macOS .dmg built in CI, and the docs for steps 3 and 4 (P11a step 6, macOS half)
cargo-packager's dmg format (it makes the .app first) from
deploy/packaging/packager-macos.toml, an Info.plist merged into the
generated one, build-macos.sh in portable shell (BSD sed) and a
package-macos recipe; the release workflow gets a macos job on
macos-latest that uploads the .dmg as a run artifact and attaches it on a
tag. Unsigned and not notarized: that is step 5 (the owner's Developer ID,
Q-4). The config was parsed under cargo-packager 0.11.8's strict schema on
Linux; the build itself needs the CI run. No new dependency.
docs/packaging.md (macOS rows, finding 7 resolved, the staged update),
architecture.md and testing.md describe steps 3 and 4.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HS8WZziDnyxunC23MUxrLp
2026-10-29 16:25:34 -06:00
swaitsandClaude Fable 5.1 01aff4c04e test(audit): every defect keeps a named regression test (P10)
The audit's 24 defects must not lose their reproductions quietly; the table
names the test guarding each, checks the runbook names only tools that
exist, and records the mirror and its For-P12 targets in docs/testing.md.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HS8WZziDnyxunC23MUxrLp
2026-10-29 16:16:28 -06:00
swaitsandClaude Fable 5.1 45bcce679b feat(audit): books.audit, the attestation tools and the CPA gate (P8)
The whole re-audit through the tools: books.audit previews its checks, then
with the preview's digest runs them, files the workpaper and journals the
run; the same books, day and scope return the run that exists.
books.attest_receipt, books.review_entry and books.attest_period record a
named client's claim with its self-reported method; books.attestations
weighs each against policy.attestation_bar as it stands. Under
policy.cpa_package_requires_audit = refuse, books that declared audits
cannot export the CPA package without a counted audit for the handoff at the
current head unless a reason is given, which is journaled as a decision
(D-9). Needs attention asks for the month-end attestation, the annual audit
and the audit before the handoff. Rules B-45, the handbook section, the
interface, parity, receipt-store and testing records, and the As-built
block. Merge needs no go-ahead: the live books refuse the new writes until
the owner gives it.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HS8WZziDnyxunC23MUxrLp
2026-10-29 15:54:26 -06:00
swaitsandClaude Fable 5.1 7bac25496e test(agent): scripted evals follow only next_steps; the surface over the wire (P5)
Agent reason: the envelope is only worth having if an agent can do its work
by following it. crates/ledger-cli/tests/agent_evals/ holds six scripted
transcripts, no model: onboarding to a first post, a receipt-backed post, a
draft that waits for its receipt and posts when it arrives, a reversal, a
reconciliation, a month-end close. agent_evals_follow_only_next_steps plays
each against a real books server: after books.start a step may call only a
tool (or fetch only a prompt) the previous result's next_steps named, with
the arguments the step gave and the ones the agent decides; setup calls (the
chart and the books an agent finds) are not the agent's moves.

agent_surface.rs holds the over-the-wire tests of the rest: the journal in
words (order, subject and type filters, paging, a bad subject refused),
source corrections through the three tools, the prompts rendered against
their files and refused when malformed, the resource templates advertised
and read, books.start v2 whole and under 8 KiB with the self-report in the
access log only, a new book told to onboard, and a mutating call answering
with the head the journal has and a refusal naming its remedy.

Docs: testing.md's P5 section (every test, and the For-P12 paragraph naming
what a mutant would flip), parity.md (no feature moved; one row), and
architecture.md (the envelope and the guards); mcp-interface.md's overview
sections now describe the surface as built.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HS8WZziDnyxunC23MUxrLp
2026-10-29 15:48:42 -06:00
swaitsandClaude Fable 5.1 3058b5c229 feat(packaging): the trial's Linux column and the Linux packages (P11a steps 1 and 3)
The Linux half of the packaging trial, run on this machine and in Debian,
Fedora and Arch containers, and the recipes it produced (docs/packaging.md
holds the table and what it found):

- mise.toml pins cargo-packager 0.11.8 and cargo-generate-rpm 0.21.1, now
  that `just package-linux` calls them. cargo-packager-updater is a
  library, not a tool, so it is not pinned (update.rs does the checking
  with minisign-verify, which is what the updater uses).
- deploy/packaging/: packager.toml (.deb, AppImage, the pacman tarball and
  its PKGBUILD), the rpm table in crates/ledger-cli/Cargo.toml, the AUR's
  own PKGBUILD (repacks the release tarball), build-linux.sh,
  repo-indexes.sh (the apt and dnf indexes, unsigned until the signing
  step), local-release.sh (a throwaway-keyed feed to try `update`),
  test-linux-packages.sh (linux_packages_install_and_upgrade: install
  0.1.0, open books as an ordinary user, upgrade to 0.1.1, check the host
  restarted onto the new file, the books' last journal line unchanged and
  `update` refuses; passes on Debian trixie, Fedora and Arch). A package
  marks itself package-managed with share/wanderbooks/install-method.
- The icon is a placeholder square (the AppImage cannot be built without
  one).
- .github/workflows/release.yml: on a version tag, build on ubuntu-24.04
  (the oldest glibc the packages claim: a program linked on this machine
  needs GLIBC_2.43 and does not start on Debian 13), attach the packages to
  the release, and add them to the apt and dnf repositories on gh-pages.
  No signing and no macOS or Windows job yet.
- The release profile strips symbols.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HS8WZziDnyxunC23MUxrLp
2026-10-29 15:12:18 -06:00
swaitsandClaude Fable 5.1 6a66a2855b feat(drafts): the drafts tools, the preview digest on every confirm, the digest tool (P4)
Accounting reason: a confirm must act on the thing that was previewed.
Every tool with confirmed (post_entry, reverse_entry, run_schedules,
dispose_asset, reconcile, close_period, create_invoice, create_bill,
write_off_invoice, purge_tin, consolidate_duplicates, and the S6
settlement tools receive_payment and pay_bill, which gain confirmed and
preview on a clone of the ledger) returns preview (SHA-256 of the
canonical preview) and refuses a confirm whose preview is missing or
stale (stale_preview, before anything is written). A draft's digest is
its own state and the settings seq; an operation's is the tool, its
arguments as sent, the settings seq and the journal head seq, so another
session's write forces a re-preview (D-50).

New tools books.list_drafts, books.set_draft_state, books.discard_draft,
books.preview_entry; reports carry as_of_status (drafts open on or before
their end, unreconciled accounts, settings problems) and take strict; the
CPA package is refused while a draft dated in it is open unless
allow_open_drafts gives a reason, journaled as a decision;
books.digest takes since_seq (what was posted since a journal line,
backdated entries included: defect 6) and lists zero_net_accounts,
drafts_by_state and the reconciliation split. Needs attention gains
'draft awaiting receipt' beside 'draft not posted'; the drafts handbook
is rewritten. Every over-the-wire harness confirms the way an agent does.

Docs: mcp-interface, rules B-43, parity, testing (with the For-P12
paragraph), architecture, and the unit's As-built block.

Tests: drafts.rs (a_draft_waits_for_its_receipt_and_posts,
the_gate_moved_and_the_mcp_error_is_unchanged,
a_reversal_needs_no_receipt_of_its_own, discard_unblocks_the_close_over_the_wire,
report_warns_on_draft_before_as_of, cpa_package_refuses_open_draft,
stale_preview_refused, every_confirm_tool_takes_a_preview,
digest_since_seq_is_posting_time, legacy_reversed_replays_identically,
gui_and_server_gate_agree), attention_parity.rs, format_upgrade.rs.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HS8WZziDnyxunC23MUxrLp
2026-10-29 13:55:38 -06:00
swaitsandClaude Fable 5.1 d8816f9ee4 docs: tokens in a file, no keyring (U0e)
Deploy recipe loses its D-Bus dance, install.sh its macOS skip, README and
site their macOS caveat; packaging, architecture and testing records follow;
U0e As built filled in (D-63).

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HS8WZziDnyxunC23MUxrLp
2026-10-29 13:31:42 -06:00
swaitsandClaude Fable 5.1 d839e709ce merge: P13 steps 4 and 5, the laws in Lean, the spec text, the derivation, GnuCash as the second engine (branch P13, f8163381)
check / check (push) Canceled after 0s
check / portable (macos-latest) (push) Canceled after 0s
check / portable (windows-latest) (push) Canceled after 0s
two-user / two-user (push) Canceled after 0s
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HS8WZziDnyxunC23MUxrLp
2026-10-29 13:08:01 -06:00
swaitsandClaude Fable 5.1 caad35d75b docs(handbook): the invoices-and-bills procedure, whole (P4b S4.5)
Step 1 now covers the issue: preview first (nothing written, the DRAFT PDF,
an abandoned preview uses no number), confirm only after the human has
seen it, the filed issued copy that a later look never replaces,
quantities and prices, the PO, and the snapshot of the customer at issue.
Step 6 is the CPA's list: agings tied to 1200 and 2000, sequential numbers
with voids visible, bills on their real accounts with the vendor's number
and no duplicate, card payments identified for 1099-NEC, deposits as
liabilities, light governance by decision.

A {documents_v2} placeholder (handbook::section_with, the server's
documents_v2 feature test) says "your books have not declared documents_v2
yet" before the owner's upgrade and what is on after it; no other section
changes. Parity, testing (with the For-P12 paragraph) and mcp-interface
notes for S4.5.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HS8WZziDnyxunC23MUxrLp
2026-10-29 12:55:10 -06:00
swaitsandClaude Fable 5.1 f81633811b docs(bookspec): P13 steps 4 and 5 test record, For-P12, and As built
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HS8WZziDnyxunC23MUxrLp
2026-10-29 12:51:30 -06:00
swaitsandClaude Fable 5.1 5e220ccada merge: P11a step 0, the macOS and Windows keyring stores and the portable CI job (branch P11a, ee4f8cd5)
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HS8WZziDnyxunC23MUxrLp
2026-10-29 12:47:08 -06:00
swaitsandClaude Fable 5.1 bb1eb84200 refactor(reader): extract.rs moves into ledger-reader (U0d)
D-61 (Q-9): the writer sheds the typesetter and the crate map is layered
again. The PDF/XLSX reader (extract.rs, plan P6) is now the plain library
crate ledger-reader, moved whole with git mv and no behavior change.
ledger-store depends on it instead of ledger-import, so the store no
longer builds Typst or the fonts; ledger-import re-exports it as
ledger_import::extract so gui, mcp, host and the fuzz targets compile
unchanged, and still never depends on the store. New crate carries only
the dependencies extract.rs imports (hayro, hayro-interpret, kurbo, zip,
quick-xml, thiserror); fuzz/Cargo.lock gains the new package.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HS8WZziDnyxunC23MUxrLp
2026-10-29 12:45:14 -06:00
swaitsandClaude Fable 5.1 ee4f8cd58a feat(host): Keychain and Credential Manager token stores, a macOS/Windows CI job (P11a step 0)
The agent path was Linux-only because tokens had a keyring store only
there (every other OS hit NOT_YET, so `connect` and the bridge failed).
D-56's destination is one installation for macOS, Windows and Linux.
Both new stores sit behind the same Items trait as the Secret Service,
so INVARIANT 4 (one account, one item, locked is an error) is kept once
and tested once through the file keyring. Windows credentials have a
unique target name, so a probe stands in for a search.

New dependencies (ledger-host only, target-gated): apple-native-keyring-store
(feature keychain) and windows-native-keyring-store, the stores the
keyring-core 1 API we already use is published with. No ledger-core change.

The `portable` job builds and tests the workspace on macos-latest and
windows-latest. Windows is allowed to fail: the rest of the host is
Unix-only (inventory in docs/packaging.md). Not verified locally: no
macOS or Windows target is installed; CI is the evidence.

Also pins cargo-packager, cargo-packager-updater and cargo-generate-rpm
in mise.toml for the trial, and records that the trial is not yet run.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HS8WZziDnyxunC23MUxrLp
2026-10-29 12:41:01 -06:00
swaitsandClaude Fable 5.1 bcd1048bb8 docs: the paused wave has landed; D-60, how much checking a merge gets; Q-9
check / check (push) Canceled after 0s
two-user / two-user (push) Canceled after 0s
The plan and the status documents say what is true after the merges
of 2026-10-29: served.md section 0, the 8.0.1 rows and unit Status
lines for S4.4, S5, S6, S7 and P6 (landed, with commits), the 8.0.2
resume paragraph turned into the record of what the merges found,
P6's At-merge bullet (the write-off and dedup interaction, the five
tests its interrupted gate never reached, schema 18), P9a's Done-when 1
verified by the green two-user job, D-58 and D-59 in order.

D-60 (the owner): per merge a compile and the unit's own tests; the
exhaustive gates once per batch, at the end. AGENTS.md's two gate
sentences, HANDOFF.md's merging procedure and the plan carry it, with
the reason: five full gates around five merges took twelve hours and
the one gate at the end found the same five tests.

Q-9 (open, for the owner): P6's `ledger-store` -> `ledger-import`
dependency, with the options and the recommendation; architecture.md
marks the arrow as under review. testing.md records the merge's test
changes under P6. STATUS.md gains "Now (2026-10-29)" with the gate
figures (170 suites, 2,944 tests, 77 minutes), the CI state and the
next steps; HANDOFF.md, roadmap.md, gui.md and the parties doc drop
the paused-branch wording; README counts 129 tools and about 2,950
tests.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HS8WZziDnyxunC23MUxrLp
2026-10-29 08:23:51 -06:00
swaitsandClaude Fable 5.1 9351f89d38 merge: P6, the evidence pipeline (branch P6, checkpoint ce366408)
The last of the paused branches, from its "not for merge" checkpoint:
extraction in ledger-import (text, pages, page images; capped, caught),
journaled as attachment.text_extracted; dedup by hash with
books.find_attachment and books.consolidate_duplicates; annotations and
books.set_entry_settles; the company record categories; the two fuzz
targets; feature attachment_text. Nineteen files conflicted with the
four P4b merges, resolved as plan 8.0.2 says: P6 retakes schema 18 (it
had taken 16, which S4.3 holds; S5 holds 17); the tool table is 129 and
the destructive count 31; both sides' appends kept with their seams
restored; one B-27 paragraph naming every feature; in the old-books
fixture P6's hand-written reimbursement is entry 7 (its ids collided
with S4.3's invoice); the replay fuzzer's list gains S7's
invoice.written_off; S4.3's issued-PDF test expects the extraction
record P6 journals right after a filing.

One integration defect between S7 and P6, found by the arap property
test and fixed here: under P6's dedup, a write-off whose evidence bytes
were filed already (the same collection letter for a second write-off of
the same invoice) was refused only after its entry had been journaled,
at the final link to the invoice ("already linked"). The write-off now
resolves the dedup before anything is journaled and keeps a link the
record already has to the invoice instead of refusing it; an explicit
existing_attachment_id already on an entry is still refused (S7's
one-receipt-once rule). P6's gate never ran before the pause; it runs on
main after this merge, and its ledger-store -> ledger-import dependency
is the open review point (plan 8.0.1).

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HS8WZziDnyxunC23MUxrLp
2026-10-29 05:05:34 -06:00
swaitsandClaude Fable 5.1 7ebeeed2e6 merge: P4b S7, bad debts (branch S7, 4b4fecb9)
The fourth of the paused branches: Ledger::write_off and the bad_debts
control role, invoice.written_off with its evidence filed in the same
store call, books.write_off_invoice, the write-off on the timeline and
in Needs attention, Lean writeOff_within_bounds, rules.md B-41 and the
bookspec examples. Twenty-eight files conflicted with the S4.4, S5 and
S6 merges; resolved as plan 8.0.2 says, keeping both sides' appends with
their seams restored, and: the scope table at 125; S7's law numbered 37;
B-41 placed before B-42; the handbook's step 4 and recovery procedures
folded into the one invoices-and-bills section; S7's wire tests in their
own module with the confirm S4.4's contract needs; S7's copy of the
payment steps dropped (S6 replaced them with Ledger::settle) and its
write-off journaled through S6's journal_auto_lines; in the old-books
fixture S7's hand-written write-off moved to a second invoice, since
S6's hand-written payment settles invoice 1 in full.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HS8WZziDnyxunC23MUxrLp
2026-10-29 02:59:55 -06:00
swaitsandClaude Fable 5.1 fc7ba81f12 ci: mutation testing is local only (D-59); the weekly run keeps proofs, oracle and fuzz
The owner, 2026-10-29: mutation testing is for local runs, not CI. The
weekly workflow's mutation shards (core, store, import, config, MCP) are
removed; they failed every week on the survivors and timeouts D-33
deferred to P12, and their red hid the run's real findings (a
fuzz_store_replay crash and a fuzz_settings_replay build break, both
being fixed). just mutants-* stays for the desktop; P12 runs the pass
whole. Recorded as D-59; the docs that said the shards keep running are
corrected.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HS8WZziDnyxunC23MUxrLp
2026-10-29 01:28:50 -06:00
swaitsandClaude Fable 5.1 2ad192bbcd merge: P6, the evidence pipeline (branch P6, checkpoint ce366408)
The last of the paused branches, from its "not for merge" checkpoint:
extraction in ledger-import (text, pages, page images; capped, caught),
journaled as attachment.text_extracted; dedup by hash with
books.find_attachment and books.consolidate_duplicates; annotations and
books.set_entry_settles; the company record categories; the two fuzz
targets; feature attachment_text. Nineteen files conflicted with the
four P4b merges, resolved as plan 8.0.2 says: P6 retakes schema 18 (it
had taken 16, which S4.3 holds; S5 holds 17); the tool table is 129 and
the destructive count 31; both sides' appends kept with their seams
restored; one B-27 paragraph naming every feature; in the old-books
fixture P6's hand-written reimbursement is entry 7 (its ids collided
with S4.3's invoice); the replay fuzzer's list gains S7's
invoice.written_off; S4.3's issued-PDF test expects the extraction
record P6 journals right after a filing.

One integration defect between S7 and P6, found by the arap property
test and fixed here: under P6's dedup, a write-off whose evidence bytes
were filed already (the same collection letter for a second write-off of
the same invoice) was refused only after its entry had been journaled,
at the final link to the invoice ("already linked"). The write-off now
resolves the dedup before anything is journaled and keeps a link the
record already has to the invoice instead of refusing it; an explicit
existing_attachment_id already on an entry is still refused (S7's
one-receipt-once rule). P6's gate never ran before the pause; it runs on
main after this merge, and its ledger-store -> ledger-import dependency
is the open review point (plan 8.0.1).

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HS8WZziDnyxunC23MUxrLp
2026-10-28 23:08:59 -06:00
swaitsandClaude Fable 5.1 335a3eaa7e merge: P4b S7, bad debts (branch S7, 4b4fecb9)
The fourth of the paused branches: Ledger::write_off and the bad_debts
control role, invoice.written_off with its evidence filed in the same
store call, books.write_off_invoice, the write-off on the timeline and
in Needs attention, Lean writeOff_within_bounds, rules.md B-41 and the
bookspec examples. Twenty-eight files conflicted with the S4.4, S5 and
S6 merges; resolved as plan 8.0.2 says, keeping both sides' appends with
their seams restored, and: the scope table at 125; S7's law numbered 37;
B-41 placed before B-42; the handbook's step 4 and recovery procedures
folded into the one invoices-and-bills section; S7's wire tests in their
own module with the confirm S4.4's contract needs; S7's copy of the
payment steps dropped (S6 replaced them with Ledger::settle) and its
write-off journaled through S6's journal_auto_lines; in the old-books
fixture S7's hand-written write-off moved to a second invoice, since
S6's hand-written payment settles invoice 1 in full.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HS8WZziDnyxunC23MUxrLp
2026-10-28 21:47:33 -06:00
swaitsandClaude Fable 5.1 be58059830 merge: P4b S6, paying on terms (branch S6, 6c4480cd)
The third of the paused branches: Ledger::settle with discounts and the
processor's fee (kernel::settle, Kani settle_is_exact, Lean
settle_posts_a_document_step), books.receive_payment and books.pay_bill
with applications, books.bills_due, the agings' detail and tie-out, the
aging exports and the CPA package's aging CSVs, the handlers in
tools/settlements.rs. Twelve files conflicted with the S4.4 and S5
merges, resolved as plan 8.0.2 says: both routers; both test appends
(the shared match arm in arap_standing.rs spliced into both tests; the
shared `.unwrap()` in store.rs); the scope table at 124; one
invoices-and-bills handbook (S5's send step 1 with S6's steps 2, 3, 5
and procedures) registered once; docs/mcp-interface.md with S4.4's
create docs and S6's payment docs; S5's law renumbered 35 and S6's 36
(S1's is 34); the parity rows and sections of both. S6's wire tests
create their bills as S4.4's contract now requires (confirmed, the
vendor's number, a line account, the vendor's invoice uploaded) and live
in their own module beside S4.4's tests.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HS8WZziDnyxunC23MUxrLp
2026-10-28 21:26:40 -06:00
swaitsandClaude Fable 5.1 3a3f03d884 merge: P4b S5, sent tracking (branch S5, ad649be3)
check / check (push) Canceled after 0s
two-user / two-user (push) Canceled after 0s
The second of the paused branches. invoice.sent, Ledger::mark_sent (with
its Lean step), books.mark_sent in its own group, the two attention
items, schema 17. Conflicts, resolved as plan 8.0.2 says: the shared
over-the-wire test file crates/ledger-cli/tests/documents.rs (S4.4's
file kept whole, S5's test appended, its create call given the
confirmed: true the S4.4 contract now needs), types.rs (both appends
kept), docs/parity.md (the review's oracle row and S5's new row).

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HS8WZziDnyxunC23MUxrLp
2026-10-28 21:21:21 -06:00
swaitsandClaude Fable 5.1 af553a3706 docs: the 2026-10-28 drift review; the plan, status and docs brought to date
check / check (push) Canceled after 0s
two-user / two-user (push) Canceled after 0s
The planning session read plan/served.md, every design document and the
five branches the owner paused on 2026-10-27 against main (44ee4083),
with dry-run merges touching no branch. plan/2026-10-28_DRIFT_REPORT.md
lists every divergence and judges every recorded departure (all kept;
three owed back; one needs a decision: P6 made ledger-store depend on
ledger-import).

The plan: section 0 "What changed on 2026-10-28"; 8.0.1 and the unit
Status lines name S4.4, S5, S6, S7 and P6 as built but unmerged; 8.0.2
records the destination (D-56: the installed app, P11a starts now) and
the resume order with the known conflicts (the shared documents.rs
test file, the types.rs appends, P6 retaking schema 18, the law numbers
34 to 37); 8.0.3 lists the names the EM approved during the wave; P11a
gains step 0 (the macOS and Windows keyring stores, the Windows data
root and pipes, a cross-platform CI build: today the agent path runs on
Linux alone); Q-5 is due and Q-8 asks whether D-47 to D-54 stand; the
Landlock sentences in section 2 and server.toml are struck.

STATUS.md and HANDOFF.md say where things stand and how to resume;
roadmap.md and gui.md are rewritten to today. The docs lose their
stale facts: 122 tools, 2,700 tests, the parity floor at 21 with the
P4b notes the slices never wrote, the ten crates and their dependency
graph (jiff added to the core's allowed list), the cutover of
2026-10-20 recorded in capability-parity.md and parallel-run.md, the
P4b S1 law and its two Kani harnesses added to verification.md (law
34), the proof counts (51 and 9), and README, the site and install.sh
honest about macOS (the viewer runs; connecting an agent waits for
P11a's keyring store, and the installer now says so instead of failing).

just check on main at 44ee4083: exit 0 (167 suites, 2,871 passed, 0
failed, 7 ignored, the server-only build clean). No code changed.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HS8WZziDnyxunC23MUxrLp
2026-10-28 18:26:23 -06:00
swaits ce36640874 wip: pause checkpoint — P6 feature complete, gate interrupted (not for merge) 2026-10-27 14:41:34 -06:00
swaits ad649be3d5 docs: S5 step 4 landed; attention predicates in the test record (P4b S5)
The As-built block records step 4 built after U0b, its two items and
their tests; docs/testing.md names them and what P12 must mutate (the
number exemption, the open/partial check, sends.is_empty(), the two day
comparisons with their checked date arithmetic, the last-send max);
docs/parity.md says sent tracking also feeds Needs attention.
2026-10-27 13:46:43 -06:00
swaits 7c5a2fe3ce docs: sent tracking in the rules, parity and test records (P4b S5)
rules.md B-27 lists `documents_v2`, the one feature S4 to S7 share
(D-52), including `invoice.sent`; receipt-store.md lists the event;
parity.md gains the sent-tracking row (done: implemented, tested, and
golden-covered by the trial balance of the fixture book around a send);
testing.md records the tests and what P12 must mutate, and names the
two attention predicates as deferred with step 4 until U0b lands.
2026-10-27 13:24:10 -06:00
swaits 57f04dbb0c feat(mcp): payments on terms, bills due, and the agings' tie-out (P4b S6)
books.receive_payment and books.pay_bill take applications, each
document's cash with an optional discount and reason, and a customer
payment may carry the processor's fee; the result names every document it
settled and what reached the account. books.bills_due lists what to pay
and the running total by due date. The agings take detail and return the
control account's tie-out, and books.export writes ar_aging/ap_aging.

The handlers live in tools/settlements.rs (the EM's split, so S4.4's
documents.rs stays free); documents.rs keeps the documents, the void and
the control check. docs/mcp-interface.md documents every changed tool and
the new one; docs/architecture.md names the group; docs/testing.md
records the tests and P12's mutation targets.

Old names (document_id, cash_account) still work; the output keeps
remaining/status for one document. A fee's processor is named in the
description, not tagged as the entry's vendor: ledger tags are entry-wide
and tagging would move the customer's party totals and 1099-NEC.

The handbook's invoices-and-bills section gains the payment, discount,
fee and bounce-replacement procedures (S4.5 can extend it). Over the wire:
one_payment_settles_two_invoices, late_discount_needs_a_reason,
a_fee_posts_gross_to_receivables_and_net_to_the_bank,
card_paid_bill_is_not_1099_nec,
multi_document_payment_counts_once_for_1099_and_party_totals,
overpayment_refused_with_the_excess,
bills_due_orders_by_due_and_discount and the_aging_export_ties_out.

Terms cannot yet be journaled on a document (S4.3) and a bill line's
account is not an MCP field (S4.4), so bill_on_terms_with_discount,
equipment_bill_payment_is_investing_cash and the discount half of
bills_due are core tests until those land; the unit's As-built block
records this.
2026-10-27 12:59:40 -06:00
swaits f31dd2640b docs: the write-off's place among the destructive tools and the tool counts (P4b S7) 2026-10-27 12:44:45 -06:00
swaits 0322778683 docs(arap): B-41, the bookspec examples, and the four registers (P4b S7)
rules.md B-41 states the write-off: the direct method the owner chose
(A-5), Dr 6990 / Cr 1200 with the receivable tagged, at most what is
owed and at most the least owed from that day on (a partial write-off as
§1.166-3(a) allows), refused on cash-basis books (§1.166-1(e)), its
evidence filed in the same call (§1.166-2(a)); bills are never written
off (ASC 405-20-40-1); and a recovery is not a new movement (D-49): the
same year, reverse the write-off and record the payment; a later year, a
plain entry to a bad-debt-recoveries revenue account, the invoice staying
written off (IRC §111).

bookspec states B-41 for the first time and gives B-38 its example: two
new examples, bad-debt-direct-write-off (two partial write-offs of one
invoice, the balances and the trial balance worked by hand) and
arap-hand-control-posting-refused (a hand line an earlier version wrote
replays, the same line written today is refused). The example schema
gains the dated write_off event, lets a refused entry say so (an entry
posts whole or not at all; older and refused never combine), and its
refusal vocabulary gains untagged_control_line, B-38's control guard.

The registers: docs/architecture.md names the new tool group;
docs/receipt-store.md lists invoice.written_off and what replay does with
it; docs/parity.md records the slice (no row changes status, the floor
stands); docs/testing.md records every test and what P12 must mutate;
docs/verification.md the two Lean theorems and what they cover.

No figure moves: no golden file changed.
2026-10-27 12:44:45 -06:00
swaits c8541ea9fc fix(deploy): the tunnel's door binds loopback only (P9a)
Review finding: `--http` parsed whatever address it was given and bound
it. The door is the plaintext MCP service with a static bearer token;
plan 3.9 and the P9a model put a TLS proxy or tunnel in front of a
loopback listener, and no decision authorises exposing the books
unencrypted. `--http 0.0.0.0:7411` would have done exactly that.

`local::tunnel_address` now accepts only a loopback address
(127.0.0.0/8 or ::1; `127.0.0.1:7411` stays the default) and refuses
anything else with a message naming loopback and why. `server` refuses
while parsing, before it takes the lock or binds a socket; `serve`
refuses before it starts a host, so nothing is left half-open. The
bind-failure behaviour for a loopback address is unchanged (said, not
fatal, 3.9), as is the unit's hardening.

The test `the_tunnel_door_binds_loopback_only` refuses 0.0.0.0, a public
sample address and [::] through `server`, checks no socket or host was
left behind, refuses through `serve`, then starts a loopback listener and
reaches `books.start` with the bearer.

No accounting code, report or figure changes.
2026-10-27 09:35:36 -06:00
swaits 1441dabcca feat(deploy): self-host recipe, the enforced isolation flag, and the tunnel's door (P9a)
The books can live on a machine of the owner's own (plan 3.8, 8.2 P9a,
D-51). Three things make that honest instead of nominal:

- `--isolation same_user|enforced` sets `server::Isolation`, carried
  through `Served` and `BooksServer` to `books.start`. Locally the books
  stay `same_user (advisory)`; under the self-host unit the server is told
  `enforced` and reports `service_user (enforced)`. The server never
  guesses, and the literal it used to report is gone.
- `--http [ADDR]` binds the same MCP router on loopback (default
  127.0.0.1:7411) beside the Unix sockets, before the ready line, for the
  TLS proxy or tunnel in front of the server. It is the same bearer door
  and the same graceful close; a failed bind is reported and is not fatal
  (3.9). `serve` carries both flags through the host, so the system unit
  can ask for them once.

`deploy/wanderbooks-server.service` is the unit: a dedicated account, a
0700 data root, `ProtectSystem=strict`, no seccomp or Landlock (D-32), and
loopback-only networking. `deploy/README.md` is the recipe: install, setup,
move the books by `books adopt` with its head check (D-31: a copied book
is the transport), mint a bearer token, put Caddy or Tailscale Funnel in
front of the loopback door, and point Claude Code and Codex at it.

The two-account CI job (`.github/workflows/two-user.yml`) makes the
deployment and runs `crates/ledger-cli/tests/two_user.rs` as another
account: `agent_uid_cannot_read_tenant` proves the tenant root and its
journal are refused, then reads, writes, uploads, files, exports and
verifies over HTTP with the bearer (in a second book, so the moved books'
head is not advanced); `self_host_adopt_same_head` compares the moved
books' head and event count with the source clone's.
`isolation_label_is_enforced_under_the_unit` runs everywhere, including
the unit-file read that keeps the recipe from drifting.

Accounting reason for the shape: nothing here changes an entry, a report
or a figure. It changes who can reach them: the move is lossless and
verifiable at the head, and the boundary is the operating system's, not a
promise in our text. No journal code, kernel function, Kani harness or
Lean step is touched, so `just prove` is not required.

No go-ahead needed: no new journal feature or event; the live books are
unaffected.
2026-10-27 09:35:36 -06:00
swaits 26d3a9941b fix(package): books.start runs the real integrity check (U0b)
books.start passed Integrity::default() to the attention engine, so it
asserted "database matches, no file failures". On a book whose database
differs from the journal, or whose stored file is missing or corrupt, the
viewer showed the database/stored-files items and the server did not: the
two lists diverged exactly where they matter most.

Call find, which computes the check: every distinct stored blob is read
and hash-checked once (a damaged blob is still reported per attachment
filed from it), and Store::disk_check now makes the viewer's comparison
through a snapshot of its data directory instead of reporting a match.

The parity test now damages its own fixture (removes the receipt's blob,
then changes a projected account row) and asserts both lists still agree
item for item and both name the damage. On the demo books (138
attachments, 138 distinct blobs, debug build) books.start takes about
0.26 s warm, 0.29 s cold, so the real check costs no decision.
2026-10-27 06:12:55 -06:00
swaits 0cf2f8f5c5 feat(package): one attention engine over PackageBooks (U0b)
Needs attention was computed by the viewer alone, while books.start
hand-picked a handful of next steps. The human's list and the agent's
could disagree, and a new item had to be written twice (or, as
books.start showed, not at all).

Move the engine to ledger-package over PackageBooks: one function,
find/find_with(books, today, settings, integrity), reads the ledger, the
journal's event types (an entry's origin), the attachments and their
links, the statements, the reconciliations, and the database check. The
viewer's attention module now only maps Target to Route; books.start
turns the same items into its next steps (why = the item's title, top 12),
so the list the human sees and the steps the agent gets cannot drift.

PackageBooks gains settings, journal, statements, reconciliations and
disk_check, implemented for Store and Snapshot, so both views answer
alike. The W-9, reconciliation and 1099 predicates move beside the items
to keep one copy. ledger-package gains ledger-config and serde_json (the
receipt policy and the tool arguments the items carry).

Tests: view_model.rs and spec_kept.rs pass unchanged; the new
attention_parity.rs compares the viewer and books.start item for item on
the demo books and on a book written entry by entry. just check green.
2026-10-27 06:12:55 -06:00
swaits 1855aa938e docs: drop the rebase marker from the P13 testing section (P13) 2026-10-26 21:23:16 -06:00
swaits cb4e955942 feat(bookspec): rule identifiers from B-1 to B-45 (P13)
Every rule of `docs/spec/rules.md` now has one bookspec identifier,
`BS-<area>-<n>`: eleven areas (entry, ledger, statement, period, partner,
asset, prepaid, arap, revenue, 1099, format), numbered in B-rule order
within its area. The table is `bookspec/spec/rules-index.md`, and each
stated rule carries its id under its heading in `docs/spec/rules.md`, so
a reader of either can find the other.

rules.md's DECISION status becomes bookspec's CHOICE: the specification
names the options and an engine declares its pick (`Claims::with_choice`),
which is what an example already meant by `status = "CHOICE"`. B-39 to
B-45 are allocated in `plan/served.md` 8.0.3 and not yet stated: they
carry an id and an area and no status, so a later unit fills them in
without moving anyone else's number.

The catalogue names the examples that demonstrate each rule (56 files,
each under at least one rule) and the rule each refusal reason belongs
to. A rule with no example cannot be claimed: a claim needs evidence.
`docs/architecture.md`, `docs/testing.md` (tests and the For-P12
mutation targets) and `docs/parity.md` record the new shape; the
bookspec README points at the checker, the runner and the adapter.
2026-10-26 21:23:16 -06:00
swaits 3e65c83f96 docs: B-26's TIN separators and P7's mutation targets (P7)
rules.md B-26 now says what the scanner reads: a comma or a parenthesis
between a TIN's 3-2-4 or 2-7 groups, and any run of spaces counting
once, while an amount's comma-grouped thousands stay an amount. The
filing scan's (a) and (b) renderings gain commas. docs/testing.md gains
the P7 section and the For-P12 mutation targets (D-33), and plan/
served.md's P7 As-built block records the three departures.
2026-10-26 20:29:15 -06:00
swaits e9febc5694 docs(testing): the P4b S1-B, S2 and S3 sections (U0c)
The 8.0.4 bar asks every unit to record what P12 must mutate (D-33), and
P4b S1 part A has its paragraph while part B, S2 and S3 shipped without
one. S1-B's targets are the payment details (a reference is one line of
at most 200 characters, carried in the entry's description; the
instrument is tagged in the same call) and the two handlers that put
B-38 on the wire (void, control check). S2's are the status filters and
the timeline labels that make a reversed payment read as reversed
instead of disappearing. S3's are the stamp arms and the re-created-copy
branch, where an invoice that has no kept copy says so.

The S2 section records what shipped against the plan's As-built words:
the timeline labels reversed items ("Payment reversed", "Payment
(reversed later)"); the viewer draws no strikethrough.
2026-10-26 15:58:27 -06:00
swaitsandClaude Opus 5.5 ebb5fcb46f refactor!: files only by handle: uploads in, downloads out (D-46)
check / check (push) Canceled after 0s
The owner (2026-10-23, via the question tool): "Uploads only"; and
before it: "What we would DEFINITELY want to avoid is passing a bunch
of BLOBs through AI context windows. That's just a pure waste." No file
passes through an agent's context, in either direction.

In: the inline arguments are gone with the 256 KiB limit (INLINE_MAX,
take_file's inline branch): content_base64 and its sha256 on
books.attach_receipt and books.file_document, w9_content_base64 and
w9_sha256 on books.set_w9, csv_text on the two CSV imports. A file comes
only by books.begin_upload and `wanderbooks upload`, named by its
upload_id; existing_attachment_id still names bytes stored already. The
five filing tools deny unknown fields, and any inline argument to any
tool is refused before it runs as an unknown argument pointing to
books.begin_upload (access log: refused inline_file).

Out: books.export (every format), books.asset_labels and
books.get_receipt view bytes hand the file over (HandedFile: download
command and expiry, D-29), as the CPA package and the W-9 do. Resource
reads stay text. A token keeps up to 64 handed files (was 16). The
access log's digest no longer exempts file bytes.

Docs: handbook files-in-out and parties-and-tins, mcp-interface.md,
rules.md, receipt-store.md, parties-and-attachments.md, plan/served.md
(D-46; the inline decisions marked superseded), STATUS.md, testing.md
(D-46: tests migrated to uploads and downloads, rewritten, removed,
added).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01T3gXMVt7XJPi6ens1or9Dj
2026-10-24 09:18:14 -06:00
swaitsandClaude Opus 5.5 d7fa16e974 feat(arap)!: documents read their figures from standing lines; control accounts
check / check (push) Canceled after 0s
refuse hand entries (P4b S1, part A)

Plan P4b (D-45), slice S1, the engine half: rules.md B-38.

- ledger-core: every receivables or payables line carries its document
  (Ledger.line_docs: DocRef, Movement, DocTag), written by the document
  commands in the step that posts it, derived on replay from the existing
  document events (the role's account as it stood then; payments by the
  ordinal rule), copied onto a reversal's mirror. Ledger::stands_on;
  doc_state / invoice_state / bill_state (total, paid, outstanding,
  status Open/Partial/Paid/WrittenOff/Void, overdue days) as of any day;
  ar_aging / ap_aging read them, so reversed payments and voids are right.
  The control guard (add_line, add_line_fx, post_entry,
  register_recurring) refuses untagged lines on receivables/payables,
  exempting a reversal's mirror of a legacy line; replay never refuses.
  Bounds (kernel::running_bounds, bounds_kept) on payments and reversals;
  a document reversal never dated before its entry; deposit/payer accounts
  must be cash-like and hold no role. New API: receive_payment_with /
  pay_bill_with (PaymentDetails: instrument, reference), void_document /
  check_void_document, control_check, payments_recorded,
  standing_payments, replay_reverse_entry. Invoice.amount_paid/status
  remain only as derived caches for the MCP layer until part B.
  cashflow bill_splits reads payments from the tags.
- ledger-store: journal feature document_standing (D-39 gated) for a
  payment accepted only because an earlier one was reversed;
  receive_payment_with / pay_bill_with (instrument journaled as
  entry.instrument_set, reference in the description), void_document;
  replay uses replay_reverse_entry; schema 15 drops the projection's
  amount_paid/status. Fixture p4-arap-before + golden figures written by
  the version before S1, adjudicated in arap_standing.rs.
- ledger-gui: party card, cash "coming up" and company owed totals read
  doc_state as of today (late from the derived state).
- Lean Documents.lean (control_agrees and friends); Kani
  bounds_check_matches_definition,
bounds_kept_is_the_definition_on_books_in_bounds;
  rules B-38, B-18/B-23 amended; bookspec `older = true` for legacy hand
  lines; tests that hand-post to 1200/2000 post as replay or renumbered.

Review fixes (both reviews of part A):
- control_role_of guards only an account the role is assigned to or one
  holding its documents' lines (a chart's own 1200 Equipment or card at
  2000 is free until a document posts there); payment accounts check
  other roles by holds_role (type-checked). The MCP tests that failed on
  the guard (oracle_mcp, spec_projects) pass unchanged.
- standing_payments leaves out reversals (sorted, deduped); replayed
  tags match the movement's side; payments_recorded counts every payment
  event (tagged or not, Ledger.payment_events) and refuses overflow.
- The record caches settled (paid + discounted + written off) and owed
  (Invoice/Bill.owed, never serialized): a void never reads as paid.
- assign_role: receivables/payables refuse moving onto an account with
  untagged lines (RoleAccountHasHandLines) and, on their control account
  holding a balance, say DocumentRoleHoldsBalance (no hand entry named);
  B-23 reworded. The reversal-mirror exemption only takes a hand line
  off (even chain depth); putting it back is refused.
- DocStatus Overpaid and Unattached; aging leaves out credit balances;
  ControlCheck.attention lists out-of-bounds and unattached documents.
- stands_on counts a chain in order; indexes (doc_index, doc_accounts,
  order_pos, reversed_by) keep the derived figures linear.
- Viewer reads a post-dated document as of its own date; one state per
  row. bounds_kept's side-crossing documented as intended.
- Tests: ten new spec_documents scenarios, stronger proptests, store
  proptest arap_props (live = replay = snapshot at every date, closed
  months), fuzz ops void/payments with details, a GUI post-dated test;
  Lean Step.assign / Step.release with unattached_zero kept; model
  limits stated in docs/testing.md and B-38.
Review fixes (CPA and engineering lenses): only an assigned role's
account or one holding document lines is a control account (a chart's
own 1200 asset stays free); a role can't move onto untagged lines or off
document lines (Lean Step.assign); a void's cache never reads as paid;
no Decimal sum panics; per-document indexes keep figures linear; a
replayed tag matches its side; the feature gate counts payment events;
a payment's reversal is never listed as standing; an overpaid document
has its own status; the viewer keeps post-dated documents.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01T3gXMVt7XJPi6ens1or9Dj
2026-10-24 09:17:42 -06:00
swaitsandClaude Opus 5.5 7741e96eb3 refactor!: one way to serve books: the stdio server is gone (D-44)
check / check (push) Canceled after 0s
The owner (2026-10-23): "We should probably just delete the stdio path
altogether. THere's no point in that. And it sitting there unused will
just lead to bitrot. Nuke it?"

Books are served only by the books server (`wanderbooks server`),
reached by agents through the bridge (`wanderbooks mcp --book ID`),
which stays.

Removed: `wanderbooks dev-stdio`, the `ledger-mcp` binary, and
`ledger_mcp::run` (stdio and `--http` over a bare data dir), with all
that only that mode had:
- files the books make coming back inline: `HandedFile.content_base64`
  is gone, and `download` and `expires_at` are always there;
- books with no access log or backups: `books.access_log`,
  `books.backup` and `books.back_up_now` are always offered, by scope;
- the settings file read and reloaded (`legacy_files`), the vault key
  named by a file or `--vault-key-file`, and the books dir's
  key-inside-the-books and git checks (with `paths.rs`);
- `wire.rs` (the bridge frames an agent's lines itself);
- `books.settings`' file fields and `books.start`'s `settings_source`.

`BooksServer` is built one way, from `Served` (a book as the books
server serves it), in production and in the unit tests. An unknown tool
is now a protocol error (mcp-tools.md). Inline file input is unchanged.

Then the owner's three decisions (2026-10-23, the question tool):
(a) `books.import_settings` is removed entirely (118 tools): old books
come in whole by `wanderbooks books adopt`, which reads their settings
file once; ledger-config keeps its settings-file parsing only as far as
adoption uses it (`load` and the path-reading `effective_receipt_policy`
are gone), and `books.chart_template` gives its settings as
`books.set_setting` changes. (b) The viewer's loose-books-folder mode is
removed (`--data-dir`, `--books-toml`, `--vault-key-file`, the settings
file and config.json followed): it opens only books of the data root,
reading settings from the journal and the vault from the book's own
`vault/` and `keys/`. (c) `wanderbooks dev-stdio` has no stub: it is an
unknown command like any other word. Also: an upload's expiry past the
calendar is an error, never an empty string.

Tests: every suite that drove the stdio server moved to
crates/ledger-cli/tests/ and drives a real `wanderbooks server` on a
data root of its own (tests/common), handed files by their download;
mcp_stdio.rs is mcp_journeys.rs. Tests of what is gone are deleted and
listed in docs/testing.md. Docs: plan D-44 (D-18 superseded), STATUS,
testing, rules, mcp-interface, the handbook, README; justfile recipes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01T3gXMVt7XJPi6ens1or9Dj
2026-10-23 19:45:18 -06:00
swaitsandClaude Opus 5.5 d64e0e9cdf fix(k1 tax basis): odd cents of property basis run cumulatively (B-36, D-38)
check / check (push) Canceled after 0s
The tax-basis K-1 shared each entry's built-in gain, property without a
basis, prepaid at book and property distributed among the members entry
by entry, every tied odd cent to the first member: three equal odd-cent
properties contributed 50/50 left the members three cents apart.

Apply D-38's rule (the closes' cumulative largest remainder, through
kernel::apportion_cumulative): tax_capital::RunningSplit keeps each
member's exact running share, rounds the running shares afresh to the
cent and hands out the change, so no member's running share is ever a
cent from exact and tied cents alternate. A negative running whole is
rounded as its magnitude, a zero one by the sign of the whole just
split, so a run of losses mirrors its gains part for part. Exact running
shares are taken to ten places before rounding: a sum of Decimal thirds
can sit a hair under its cent (the law found 399.93 as 399.9299...).

tax_capital::entry_parts runs the splits over every posted entry from
the books' first, in date order, whatever span is asked for, with one
count per set of members with a part in the entry: a member never takes
a cent of an entry they had no part in (Item L's capital contributed is
the basis of the property that member contributed), and the count never
restarts, since its weights are each entry's own amounts, not shares.
An entry's reversal takes its original's split back out of the count
(RunningSplit::unsplit), so an entry's cents are the same in every span
and next year's beginning is this year's ending. The K-1's per-entry
lines come from k1::entry_lines, which k1_detail now uses too.

The book property each member contributed in an entry stays split entry
by entry (each member's property must stay within their own credit);
B-36 says so.

Tests: tied_odd_cents_of_property_basis_run_cumulatively (fails on the
per-entry split: 665.86 / 665.83); a_member_outside_an_entry_never_
takes_its_cents (three members; fails with one count for all members),
an_entrys_odd_cent_is_the_same_in_every_span (a reversal across the
span's start; fails with the count carried span by span or the reversal
not taken out), the_split_runs_in_date_order (fails in posting order);
the law a_running_split_never_drifts_a_cent (5,000 cases: the
largest-remainder rounding of exact integer shares, ties to the first,
and the negated run mirrored; fails with ties to the later member or a
zero running whole rounded as a gain); unit tests for one split,
alternating ties, a member outside a split, the mirror at zero, unsplit
and the thirds case; bookspec example partnership-k1-tax-basis-odd-cents.
rules.md B-36, the handbook (tracing figures), STATUS, testing.md and
verification.md updated.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01T3gXMVt7XJPi6ens1or9Dj
2026-10-22 19:31:18 -06:00
swaitsandClaude Opus 5.5 535874e0f3 fix(cash flow): a payment to a government is other operating, by its payee
The P3 cutover review's open point 2: a state's LLC filing fee, booked
to an organization-costs account (tax category organization_startup),
read as "Cash paid to suppliers and employees". B-14 rule 5 sent a
payment to class (g) only when every operating line's account was
taxes_licenses, so the account, not the payee, decided.

ASC 230-10-45-17, read in EY's guide that reproduces it (Statement of
cash flows, July 2026, p. 43; the PDF re-fetched and its SHA-256
recorded in docs/spec/sources/fasb-asc.md), lists apart "(b) Cash
payments to other suppliers and employees for other goods or services"
and "(c) Cash payments to governments for taxes, duties, fines, and
other fees or penalties". 45-25's classes name only income taxes (f)
among the latter, so the rest are (g), other operating cash payments.
A formation filing fee is rightly an organization cost (its account and
tax line), and still a payment to a government: the payee is what
45-17(c) is about.

So rule 5 now sends a payment's government part to (g): of an
operating payment the structure classes (d), what went to a party with
the government_agency or tax_agency role (the chain root's parties, so
a reversal keeps its original's class) is (g), on any account; and, as
before, a payment whose every operating line is on a taxes_licenses
account is (g). Every party a government: the whole payment. A
government and a supplier named together: the payment is split by their
resolved shares (B-35), the government's part (g), the rest (d), since
ASC 230-10-45-22 has "each separately identifiable source or each
separately identifiable use" classed "on the basis of the nature of the
underlying cash flows" (EY p. 44; quoted in fasb-asc.md; it names the
three activities, read for 45-25's classes too as a CONVENTION). The
government's part is the payment x its share / the whole, rounded half
to even to the base currency's places, the rest exact, so the line still
sums to the payment and a reversal mirrors it. Shares that don't
resolve leave the whole payment (d). One naming no party is decided by
its accounts. Receipts are untouched (a refund from a government stays
(c)). No total changes, only which operating row a payment is in.

Two limits, from the review of this change: a government selling goods
or services (postage) is 45-17(b)'s supplier "for other goods or
services", so the rule says, and the handbook tells the agent, to add
such a payee as a vendor, never with the government_agency role (the
books can't tell what a payment bought; the role says it). And payroll
keeps its structure (CONVENTION): an entry with a line on the salary
expense or a payroll payable control account, or on a pay tax category
(salaries_wages, officer_compensation, employee_benefits,
pension_profit_sharing), stays (d) whoever it names: withheld taxes
remitted are the employees' pay, and the payables mix them with the
employer's taxes, which a remittance can't tell apart. So a remittance
to the IRS is (d) as it was before this change; the employer's payroll
tax paid on its own expense account is a tax, (g).

The first company's books have this case: their state filing fee moves
from "Cash paid to suppliers and employees" to "Other operating cash
payments" at every date once its entry names the state's agency with the
government_agency role and its own entry is the one whose cash the
statement lists (paid from the bank, or on a card that is a lender's
credit, D-42, or by a member through the members' account while it is
a financing account, B-14 rule 11, the way that fee was paid);
charged to an operating-payable card, its cash leaves in the card
payment, whose payee is the bank, and it stays with suppliers.
STATUS says how to check both, and that the cutover goldens' cash-flow
snapshots will differ on the two class lines only (totals the same),
so the next cutover diff isn't read as a regression.

Docs: rules.md B-14 rule 5 (the rule, its source quoted, the date and
what it was before); fasb-asc.md (45-17(b) and (c) quoted exactly, with
the guide's SHA-256); the handbook's entries section (name the agency as
the party on a tax, fee, fine or penalty paid to a government; a shared
payment named with both amounts; a government selling services added as
a vendor; payroll remittances stay with pay; a fee on a card follows the
card payment); fasb-asc.md also quotes 45-22 and 45-22A; bookspec
example cash-flow.government-fees.toml (a filing fee and a lawyer on
one organization-costs account, a license on taxes and licenses with no
party, a penalty to a tax agency on the wrong account, paper from a
supplier, an annual report paid through a registered agent, 37.00 the
state's and 100.00 the agent's; every figure by hand) with a new `class.<a..g>`
key in the
cash_flow query (bookspec README; the runner checks every class the
statement has is given); testing.md; STATUS.

Tests: ledger-core tests/spec_cash_flow.rs
`a_payment_to_a_government_is_other_operating` (the filing fee and a
penalty to a tax agency (g); the same account paid to a lawyer, or with
no payee, (d); its reversal next month keeps (g); a refund (c)),
`a_government_payee_moves_a_fee_on_any_account` (a taxes_licenses
license with no party (g); a payment shared half and half between a
government and a printer split 50.00 (g) and 50.00 (d), the government
alone all (g)), `a_shared_government_payment_splits_to_the_cent` (a
third by percent of 100.01: 33.34 (g), 66.67 (d); amounts split as
given; the reversal mirrors to the cent),
`a_payroll_remittance_to_a_tax_agency_stays_pay` (withheld taxes
remitted from a payable holding its payroll role, and pay on a
salaries_wages account, stay (d) with the IRS named; the employer's tax
on its own expense account is (g)),
`a_government_selling_services_is_a_supplier` (postage to a vendor (d);
given government_agency, (g): the limit the handbook guards); src
cashflow.rs `tests` `a_government_part_rounds_half_to_even` and the
proptest `a_government_part_sums_and_mirrors`; the bookspec example;
from the P4 review, `a_reversal_takes_its_originals_payee` (a fee and
its reversal next month land in one class, the chain's payee deciding:
the books keep a chain's parties one, so reading the reversal's own
parties is an equivalent mutant) and
`a_shared_government_payment_a_member_paid_splits_both_ways` (a shared
payment a member paid: each part with its own on_behalf; fails with
the on-behalf part unsplit). docs/mcp-interface.md: books.cash_flow
and the Roles paragraph say a government or tax agency payee makes a
payment other operating, and a shared one two operating lines.
Broken to confirm: without the payee check the five spec tests and the
example fail; without the split for mixed payees the two shared-payment
tests and the example (class.d -549.50) fail; without the payroll
exclusion the payroll test fails; rounding toward zero fails both unit
tests; without the tax-category route
`operating_classes_follow_asc_230_10_45_25`, the second test and the
example fail. The viewer's cash flow page,
rendered on made-up books before and after: the fee moved from "Cash
paid to suppliers and employees" to "Other operating cash payments",
every total the same; and on made-up books with an annual report
shared between the state and an agent: each part on its own class
line, every total the same.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01T3gXMVt7XJPi6ens1or9Dj
2026-10-22 19:31:18 -06:00
swaitsandClaude Opus 5.5 6c587086a0 feat(iif): carry each line's project in CLASS, the classes declared first
The P3 cutover review's open point 1: entries carry projects (classes,
B-37), but the QuickBooks Desktop file (B-34) left the CLASS field empty
on every line, and neither B-34 nor the README said so, though B-34 rule
6 says nothing changes silently.

Intuit's IIF Import Kit (vendored in docs/spec/sources/intuit-iif.md,
the kit's SHA-256 unchanged): the `!CLASS` header help says "Only the
NAME column is required" and a subclass's NAME is its parents' names
first, joined by colons; the kit's "Import First - List Declarations"
example declares `!CLASS` rows before any transaction, and its "2004
Deposit (Income)" example names the class line by line in CLASS. So the
file now declares every class in a `!CLASS` list after the accounts and
before `!TRNS`, and writes each line's class in CLASS (empty where the
line has none). Books with no class write no list, byte for byte as
before. A line whose class wasn't declared is refused
(IifError::UndeclaredClass), never written classless.

A class's NAME follows B-34's rules for an account's own name: plain
ASCII (rule 3), a colon written '-' (it would start a subclass),
trimmed, '?' if nothing is left, cut to 31 characters: Intuit's qbXML
Onscreen Reference gives `ClassAdd`'s Name "maxDT" : 31 (and 31 in the
Canadian, UK and Australian desktop editions; 100 only in Online), the
same as its `AccountAdd` Name (both JSON files vendored by SHA-256);
Conductor's QuickBooks Desktop API reference agrees and says the name is
case-insensitive. Classes whose NAMEs would match ignoring case get
" (id)", round after round, as accounts get " (code)", except that
among names that match, those already written differently from the
books (made safe, or tagged) yield first, a second " (id)" if need be:
a class's own name is kept unless another class's own name is the same
ignoring case (classes 1 "Café", 2 "Cafe", 3 "Cafe (1)" are written
"Cafe (1) (1)", "Cafe", "Cafe (1)"). Each class is tagged twice at most
and ids differ, so none is left alike (a hand-built list is refused). Each class name
written differently is an IifChange (IifField::ClassName), said one
line each in the warnings and in the package README, whose
"written differently" paragraph now names class names' limits; its IIF
line says CLASS is each line's project when there are classes. The MCP
and viewer notes count "account or class name(s)".

API: `iif_classes(ledger)` builds the list; `journal_to_iif` takes it
(`&[]` for none).

Docs: rules.md B-34 rule 8 with its sources; intuit-iif.md (the
`!CLASS` help, the list declarations and deposit examples, Intuit's SDK
department page, the qbXML Onscreen Reference's ClassAdd and AccountAdd
and Conductor's class reference, each with SHA-256);
mcp-interface.md (books.export iif, the package note); the bookspec
example iif-quickbooks-journal-file.toml (two projects: one carried and
renamed, one only declared; every line derived by hand); testing.md;
STATUS (open point 1 done; point 3 waits for the close, the owner said).

Tests: ledger-import
`iif_writes_each_lines_class_and_declares_the_classes_first`,
`iif_class_names_fit_quickbooks_and_every_change_is_listed`,
`iif_class_names_written_as_the_books_have_them_are_kept`,
`iif_refuses_an_undeclared_or_unfit_class`, property
`iif_class_names_never_clash`; ledger-package
`the_package_iif_carries_each_lines_project` and the bookspec example.
Broken to confirm: CLASS written empty fails the two exact-file tests,
the package test and the bookspec example; the " (id)" tag dropped fails
the names test and the property; tagging every name that meets (no
yielding) fails the kept-names test and the property (its shrunk case,
"A-B" beside "A\uFF1AB", kept in the regressions file).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01T3gXMVt7XJPi6ens1or9Dj
2026-10-22 19:31:18 -06:00
swaitsandClaude Opus 5.5 528c070605 feat: invoices and bills take a project, line by line (B-37 item 8)
A client project's invoiced revenue and a contractor's billed cost could
never count toward the project: books.create_invoice and books.create_bill
posted at once with untagged lines, and only a draft's line takes a class.
Job costing (B-37 item 3) collects a job's billings and the supplier
invoices for its direct costs on its cost sheet, so each document line
now takes an optional project.

The owner decided it on 2026-10-22 (plan D-43, rules.md B-37 item 8):
each line takes one project or none; a document spanning N projects is N
lines, each with its own amount; no percentage split, so every figure is
exact; a cost truly shared among projects names none on the document
and reaches projects only as entries of their own under the company's
monthly allocation convention, when books.conventions has one (otherwise
it stays untagged and the agent asks the human). The reasoning, stated
in B-37: job costing charges a job the costs traceable to it, whole, and
reaches shared costs only by allocation on a stated basis. ASC
340-40-25-7 counts as directly related both the costs traceable to a
contract (25-7(a), (b), (d), (e)) and allocations of costs that relate
directly to it (25-7(c)); 25-8 expenses general and administrative cost.
The books carry the first whole on the document's line and the second as
the allocation convention's own entries; neither is a percentage on the
document (sources/fasb-asc.md, 25-7 and 25-8 added). The handbook's
projects rule and the line's class parameter say the same, and no
handbook sentence lets a shared cost be split across a document's lines.

The accounting: the document's entry posts one revenue (an invoice's) or
expense (a bill's) line per project its lines name, in the order each is
first named, each the sum of that project's lines, and the untagged
lines' sum as one more line (arap::document_parts, conservation checked
by a proptest). A document naming no project therefore posts exactly the
one line every document posted before. The receivable or payable line is
never tagged: it is a balance-sheet line, which item 3 counts nothing;
the payments (receive_payment, pay_bill) move cash against it and take no
project either. Nothing but the projects moves: the P&L, the 1099
worksheet (payments made, B-19) and party totals (what moved the money,
B-35) read the same with or without the tags (twin-books test); the
projects with the untagged lines still partition the P&L.

- ledger-core: DocumentLine.class_id (serde-omitted when none, so old
  documents serialize as before); create_invoice / create_bill split the
  revenue or expense side by project and tag it; an unknown project is
  refused whole (atomically).
- ledger-store: a document line's project is journaled as class_id on
  invoice.created / bill.created, and the entry's line as
  entry.line_class_set right after its entry.line_added (the entry.posted
  lines carry class_id, as a hand-built entry's do). Behind a NEW journal
  feature, document_line_projects (KNOWN_FEATURES, declared by new books,
  asked for before the first journaled line, not even the control
  accounts written on refusal); not `projects`, which the live books
  already declared: the owner consents again at the next upgrade. A
  malformed class_id is corrupt; null or absent is none. Untagged
  documents journal byte for byte as before; old journals replay
  unchanged.
- ledger-mcp: each line takes class (id or name, resolved like
  add_line's, every one before anything is written); the result gains
  notes for a finished or abandoned project, and on an invoice for an
  on-spec project (is there now an agreement? on spec a hoped-for
  payment is no receivable, B-37 item 4), tagged anyway; books lacking
  the feature refuse with format_upgrade_pending. books.get_entry shows a
  line's class_id and project. The handbook's "an invoice or bill takes no
  project yet" caveat now depends on the books: gone where the feature is
  declared, an "until their format upgrade" note where it isn't.
- ledger-gui: an invoice's or bill's page (its entry's) lists the
  document's lines, each project a link that opens it; the demo's client
  invoices are tagged to "Client work", whose snapshot is now its revenue
  less its contractors. Looking at the page in a narrow window found a
  tagged line's tag pushing the amounts past the pane (any tagged entry):
  the name is now cut to leave the tag room, the tag itself at most half
  the cell.
- bookspec: an `invoice` event, and `lines` with `project` on a bill or
  an invoice; example projects-invoice-and-bill-lines-count-by-project,
  worked by hand, both projects client work (Zeta 3,500.00 - 1,200.00 =
  2,300.00; Harbor 200.00;
  untagged -1,750.00; together the P&L's 750.00; Dana's 1099 payments
  1,250.00).
- fuzz: fuzz_store_replay can declare journal features.
- Tests: core, store, MCP, viewer, the P2-served cutover fixture and a
  viewer/server parity test (its month lists the same length, not
  empty); a tagged document's reversal nets its projects to zero, live
  and replayed; books.get_entry's class_id; the handbook served as a
  resource both ways; each fix broken to confirm its test fails
  (docs/testing.md). Docs: rules.md B-27 and B-37 items 3 and 8,
  sources/fasb-asc.md, plan/served.md D-43,
  mcp-interface.md, testing.md, parity.md, plan/STATUS.md.

No proof changed (no kernel arithmetic: document_parts sums with the
existing document_total).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01T3gXMVt7XJPi6ens1or9Dj
2026-10-22 19:28:40 -06:00
swaitsandClaude Opus 5.5 3eda95eb3c Projects: the snapshot of a project's figures
check / check (push) Canceled after 0s
The project page opens on a snapshot: revenue, direct costs, the
contribution before overhead and its margin of revenue (a segment
margin: revenue less every cost traced to the project, fixed and
variable; B-37 item 7), in the black or red, and for on-spec work the
cost unrecovered if repaid. To date or this fiscal year. By month,
revenue and costs with the net to date in its own strip; costs by
category; the list colors each net by its sign. books.project carries
the same figures, and a parity test runs the viewer and the MCP on the
same books to the cent. Tests honor TMPDIR.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01T3gXMVt7XJPi6ens1or9Dj
2026-10-22 06:54:26 -06:00
swaitsandClaude Opus 5.5 d24653558c Tokens: one keyring item per token, read back; the bridge says a locked keyring; token check
check / check (push) Canceled after 0s
On the live books a reconnected bridge got no token from a keyring that
couldn't be read, was told to "make a token", and after
`token create --replace` kept on with the new token in memory while
`download` read a revoked one from the keyring.

- tokens.rs INVARIANT 4: keeping a token removes every item for its
  account in every collection, makes one and reads it back; reading finds
  every item and, finding several, refuses with how to keep one again; a
  locked or unreachable keyring is KeyringError::Unavailable, never
  "no token". The logic is shared by the OS keyring and the test files
  (now a folder per collection a test can lock, make unreachable, or
  plant a second item in).
- make_token reads the token back before saying anything, or revokes it.
- The bridge tells the agent the keyring is locked or unreachable and the
  token is fine (or that it holds several items), never to make a token;
  it reads the keyring again before every new session, even with a token
  held.
- `wanderbooks token check --book ID [--name N]`: whether the token kept
  here is the books' live one, asked by its SHA-256 (admin op
  token_check), never its text.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01T3gXMVt7XJPi6ens1or9Dj
2026-10-22 04:17:22 -06:00
swaitsandClaude Opus 5.5 321b94562a Projects: classes as entities, and the sidebar fits short windows
check / check (push) Canceled after 0s
A project is a class with facts (description, status, kind, party,
dates, links, notes) in one project.set event behind the format
feature projects. Four tools (books.add_project, update_project,
projects, project); documents and decisions link to projects; B-37:
on-spec costs are expensed as incurred, repayment is revenue, and the
unrecovered cost is never below zero (Kani). Viewer: a Projects list
and a project page with its figures, by month and by account. The
sidebar measures its footer instead of reserving a fixed 172 px.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01T3gXMVt7XJPi6ens1or9Dj
2026-10-22 03:50:52 -06:00
swaitsandClaude Opus 5.5 0495726001 P3 review: the surface, docs and proofs
check / check (push) Canceled after 0s
Split kernel::entry_flows into entry_movements and flows_of so the
reversal-mirror proof fits CI; prove runs the fast harnesses, prove-slow
the 8 slow ones (own job in scheduled CI), prove-all both. Laws in
verification.md renumbered 1-31 with a table of all 55 harnesses.

MCP docs checked against the 115 tools (about 60 fixes) with a test that
every tool is documented; rules.md cross-references fixed; bookspec
statuses and loader tightened; viewer notes wrap and accounts sort by
code; STATUS.md says P3 is built, cutover pending.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01T3gXMVt7XJPi6ens1or9Dj
2026-10-22 02:13:08 -06:00