Commit Graph
515 Commits
Author SHA1 Message Date
swaitsandClaude Fable 5.1 70a3f795f4 refactor(mcp): the eight long tool descriptions move into the handbook (P5)
Agent reason: a tool description is read every time the tool list is, so
eight descriptions of 800 to 2,000 characters cost every session for text
only a few calls need. books.export, books.export_cpa_package,
books.import_bank_csv, books.reconcile, books.set_entry_party, books.set_w9,
books.show and books.vendor_1099 now say what the tool is for, what it
requires and where the rest is, in at most 700 characters; the rest is in
the handbook, verbatim: the CPA package's contents (cpa-handoff), the share
rules of a multi-party tag and the W-9 procedure (parties-and-tins), the
worksheet's construction (1099), the CSV import and the report list with
the IIF limits (files-in-out), the reconciliation's answer (reconciliation)
and the targets books.show opens (viewer).

tool_descriptions_fit_700_characters now pins zero and its allowance list is
deleted: no description may exceed the cap, and none is excused. The period
test keeps its sentence about omitted dates in books.export's description.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HS8WZziDnyxunC23MUxrLp
2026-10-29 15:48:27 -06:00
swaitsandClaude Fable 5.1 453abb62ea feat(mcp): books.start v2 and the agent's self-report (P5)
Agent reason: the first call of a session should say who is asking, what is
waiting and what to do, in under 8 KiB, from the same lists the viewer
draws. books.start now carries book.id and book.url (the book's path on the
server, never a path on a disk), you {principal, kind, scopes, client_id},
drafts {building, awaiting_evidence}, reconciliation {tracked,
never_reconciled, behind}, backup {last_at, through_seq, stale}, the
prompts, and needs_attention (the twelve most urgent items, problems first,
with every item counted by kind), beside next_steps now in the envelope's
shape (tool or prompt, why, required; attention problems are required). A
new book is told to run the onboard prompt first, then to record the
company's facts and set up the chart; an active one is offered a draft
entry. The tool carries _meta anthropic/alwaysLoad, so a client that defers
tools loads it up front. The size fit shortens attention items and waiting
drafts after the convention summaries and before the lists, as before.

agent {model, reasoning_effort, harness} is accepted and written to the
call's access record (the access log shows it as agent), self-reported and
never checked, and used for nothing else: not in the answer, not in any
decision. Who asked is the request's alone: it is cleared when the request
ends, so a later resource read on the same thread cannot see it.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HS8WZziDnyxunC23MUxrLp
2026-10-29 15:48:27 -06:00
swaitsandClaude Fable 5.1 996c5f40f4 feat(mcp): prompts, resource templates and report dates on resources (P5)
Agent reason: the procedures an agent follows (onboarding, reconciling a
month, closing a month, the CPA hand-off, the year's compliance, an audit)
should be one name away, written against this server's own tools, instead of
rebuilt from the handbook each session. The server now advertises the
prompts capability (also in the bridge's cold-start initialize, which uses
the same capabilities function) and serves prompts/list and prompts/get to a
token that may read: onboard, reconcile {account, month}, month_end_close
{month}, cpa_handoff {year}, annual_compliance {year}, audit {scope}. Each
text is a file in crates/ledger-mcp/prompts/ with the company's name and the
arguments filled in. migrate, process_inbox and receipts_from_email are not
built (D-46, D-28, D-17). audit points at books.verify, books.control_check
and the digest; P8 fills in its own tools.

An argument is one token (letters, digits, - _ . :, at most 40) of the shape
the prompt asks for (a month, a year, a word from a list), so what a caller
passes can never rewrite the text; an unknown prompt, a missing, extra or
malformed argument is refused. A test checks every prompt names only tools
that exist.

The five resource templates of plan 4.5 are advertised (resources/templates/
list): books://entries/{id}, books://attachments/{id}, books://journal/{seq},
books://reports/{kind}{?start,end,as_of} and
books://digest{?since,since_seq,as_of}; the reports now take their dates
from the query (they read only today's or the year to date before), and the
digest reads since_seq (what was posted since a journal line, backdated
entries included).

Tests: the_prompts_name_only_tools_that_exist,
an_argument_is_one_token_of_the_right_shape.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HS8WZziDnyxunC23MUxrLp
2026-10-29 15:48:27 -06:00
swaitsandClaude Fable 5.1 61da9e57cd feat(mcp): books.journal_history, the journal in words (P5)
Agent reason: an agent traces a figure back to what produced it by reading
the journal, but it must never read the file. books.journal_history is the
one way: each line in order with its seq, time, type, a sentence saying what
it did to what, its subject and the entry it belongs to, filtered by
after_seq, type (exact, or a group like entry.) and subject (entry:12,
invoice:3, bill:3, account:10, party:5, asset:2), paged with more, with the
torn tails set aside on open listed beside them. It reads the verified
journal through read_journal_and_format, so a damaged chain is an error, not
a story.

The sentences come from ledger-store's describe.rs, which quotes only
identifiers, dates, amounts and the short names a person typed: never a
setting's value (settings.set names its keys) and never a full TIN (masked,
B-26). Every event type the store writes has a sentence (tested against
EVENT_TYPES). The tool is read-only (books:read); the books://journal/{seq}
and books://entries/{id} resources read through it.

Departure from the plan: the viewer's own sentences (its describe arms in
ledger-gui, which carry icons, routes and today's party and account names)
stay where they are; moving a thousand lines of egui text behind a
formatting trait was not worth the risk to the viewer's tests, so the server
has the shared describer and the viewer keeps its own.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HS8WZziDnyxunC23MUxrLp
2026-10-29 15:48:27 -06:00
swaitsandClaude Fable 5.1 4236f03744 feat(store): a record's source can be corrected without touching its words (P5)
Accounting reason: a convention, a decision or an open item carries who
decided it, when and on what authority. When that is recorded wrongly the
honest fix is to say so on the record, not to restate the policy: a policy
revision bumps the convention's revision and reads as a change of the rule,
and a new decision supersedes the old one. A source correction changes only
the authority and says why, so the audit trail shows the words were never
altered.

New event types convention.source_revised, decision.source_revised and
open_item.source_revised {id, source, reason} under the journal feature
source_revisions: an older version would refuse the types, or show the old
source as the record's authority, so it stops at the declaration instead
(rules.md B-27) and books that have not had the owner's upgrade refuse the
write with format_upgrade_pending, journaling nothing. Replay sets the
source and nothing else (the text, the revision and the journal line stay);
the projection updates the rows' existing source columns, so no schema
changed. A correction must be one: other words, the same source, no reason
or an unknown record is refused.

books.set_convention takes reason (the text exactly as it stands),
books.record_decision and books.raise_item take revises with reason. Without
reason the old refusal stands and now says how to correct only the source.

8.0.6: the hand-written lines of the three shapes in
books_written_under_older_rules_still_open; fixture old-books/p5_before,
written by b97234ac (a revised convention, two decisions, two open items, a
draft) through old-books-writer/src/bin/p5_before.rs, opens to that
version's figures, so no figure moved; books_p2_served_cut_over_whole
refuses the correction before consent and takes it after; the fuzzer and the
viewer's journal know the types. The live books refuse the new write until
the owner gives the go-ahead; merging does not need it.

Tests: a_source_only_revision_is_journaled,
a_source_revision_must_change_only_the_source,
source_revisions_wait_for_the_upgrade, source_revisions_is_its_own_feature.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HS8WZziDnyxunC23MUxrLp
2026-10-29 15:48:27 -06:00
swaitsandClaude Fable 5.1 cf73d0bda5 feat(mcp): every result carries the envelope (P5)
Agent reason: an agent should never have to guess what follows a call. One
place (call_tool, after the tool answers) now gives every tool result the
same four fields: next_steps (what to do next, each with why and whether it
is required), guards (the named refusal and the tool that remedies it),
warnings, and, on any tool that is not read-only, head (where the journal
ends after the call's last write). A tool's own field of the same name is
kept, never overwritten; the text block says what the structured content
says.

Next steps come from the tool's own result (a preview gets its confirm step
with the digest; a post is followed by showing the entry and, only if it
was in error, its reversal; a draft by its lines; a statement by marking
its lines and reconciling; a period list by the close) and, after a change
to an entry or an invoice, from the viewer's own Needs-attention items about
that subject, so the agent and the human read one list.

guards.rs is the registry of refusals (stale_preview,
format_upgrade_pending, receipt_required, approval_required, party_shares,
party_required, open_drafts, untagged_control_line, duplicate_vendor_ref,
first_number_locked), each naming a tool that exists. A refusal whose text
carries a guard gets it as structuredContent.guards; its text is unchanged.

Tests: every_mutating_result_has_head_and_next_steps (the whole router),
the_envelope_keeps_what_a_tool_said_and_carries_a_refusal_guard,
every_guard_names_an_existing_tool, a_refusal_becomes_its_guard,
every_step_a_tool_names_exists.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HS8WZziDnyxunC23MUxrLp
2026-10-29 15:48:27 -06:00
swaitsandClaude Fable 5.1 169b914e1f fix: what wave 1's gate found: B-43 speaks CHOICE, a Linux-only socket test is Linux-only
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
GitHub's check on f043db35: bookspec's `rules_md_carries_every_id`
refuses "DECISION" in docs/spec/rules.md (bookspec's word is CHOICE,
P13 step 3); P4's B-43 wrote "DECISION D-47" twice. The portable
macOS job: `a_socket_with_no_other_end_names_no_process_and_isnt_us`
uses `SOCK_CLOEXEC` and Linux's answer for a peerless socket; it is
gated to Linux, which is what it tests.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HS8WZziDnyxunC23MUxrLp
2026-10-29 15:25:37 -06:00
swaitsandClaude Fable 5.1 54312b3980 merge: P11a steps 1 (Linux), 2 and 3: the trial's Linux column, update and rollback, the Linux packages (branch P11a, d6c90a11)
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 15:21:25 -06:00
swaitsandClaude Fable 5.1 d6c90a115f docs(plan): P11a as built for steps 1 (Linux), 2 and 3
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 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 4b5f44689d feat(cli): a launch with nothing to say and no terminal is the viewer (P11a step 3)
An AppImage's launcher runs the desktop file's Exec line and appends the
arguments it was given, so with `Exec=wanderbooks gui` the AppImage's
`update` ran `wanderbooks gui update`: the program's own commands were
unreachable through it. The desktop files now say plain `Exec=wanderbooks`
and a bare launch with stdin not a terminal (a menu entry, a double-click)
is the viewer; at a terminal it is the usage, as before. The AppImage is
thereby also the command line (`Wanderbooks.AppImage update`).

Test: bare_launch_tests::only_nothing_at_all_with_no_terminal_is_the_viewer
(the launch itself cannot run under test: the viewer finds the account's
real data root from the password database, not from the environment).

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 23f9e9395b feat(cli): wanderbooks update and rollback (P11a step 2)
`wanderbooks update` reads a release manifest (default: GitHub Releases'
fixed address, D-26), refuses anything not higher than this version,
downloads the platform's file and accepts it only if its sha256 matches
the manifest AND its minisign signature verifies against the release key
compiled into the program (WANDERBOOKS_RELEASE_PUBKEY at build time;
a build without one updates from nothing but a file:// feed whose key the
owner names with --trusted-key, so a local release can be tried). The
verified file is staged under <root>/updates/<version>/, the running
version is kept under updates/previous/ with the journal format it
understood, and the program file is replaced by a rename of a complete
copy made beside it (a reader sees the old file or the new, never a
part). The new program must answer --help; if not, the old one is put
back. Nothing tells the books to stop: the host already notices a
replaced executable and restarts onto it, finishing calls in progress
(host.rs INVARIANT 7).

`wanderbooks rollback` swaps the kept version back, and refuses while any
book declares a journal format or feature the kept version does not know
(plan 3.9: after the owner consents to a newer format the only way back is
the pre-upgrade restore). A package-managed install (a package ships
share/wanderbooks/install-method beside bin/) refuses both and says to use
the package manager. An AppImage replaces the file named by $APPIMAGE.

Tests (crates/ledger-cli/tests/update.rs): update_is_atomic,
failed_update_rolls_back, unsigned_update_refused (another key, other
bytes, a sha that differs, no signature, not a signature),
bridge_survives_update, update_waits_for_a_running_call,
package_managed_install_refuses_update, the kept-version and format-gate
tests; unit tests in ledger-host/src/update.rs.

Dependencies: minisign-verify 0.3 in ledger-host (what cargo-packager's
updater uses to check release files; no dependencies of its own) and
minisign 0.10 as a ledger-cli dev-dependency (the tests sign with a key
they make). sha2 and the two minisign crates are built optimized in debug
and test builds: the tests hash a debug build's program. The release
profile strips symbols, for the packages (P11a step 3).

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 f043db35ca docs(plan): wave 1 complete (P4, U0e landed); wave 2 building
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 14:03:39 -06:00
swaitsandClaude Fable 5.1 b97234ac7e merge: P4, draft states, the evidence gate in one place, the preview digest, the digest tool (branch P4, 6a66a285; schema 19)
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 14:01:08 -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 7299cf90f2 feat(evidence): one function decides whether an entry needs evidence (P4)
Accounting reason: the receipt rule (Pub. 583, Rev. Proc. 98-25: the
books must be demonstrably supported by the records) was decided in
three places that could disagree: the server's post, the digest, and a
string match on a journal-event origin in the viewer. ledger_core::
evidence::requirement is now the one decision: Required (with why) or
Exempt (with the source). A reversal needs no receipt of its own (its
evidence is its original's, D-47, D-12); neither do a closing entry, a
schedule month's or a depreciation run's posting, a disposal, a
document's entry, a payroll run or its remittance. The ledger learned
the two origins it could not see (a disposal, a remittance) from the
events that already name their entry, live and on replay. A split group
counts for a draft only: a posted entry's decision was made when it
posted.

The policy moved with it (evidence::Policy; ReceiptPolicy in
ledger-config keeps the words and delegates). Store::post_entry_checked
refuses a covered draft with no receipt (StoreError::ReceiptRequired,
which the MCP maps to the unchanged receipt_required error);
Store::post_entry stays the primitive that commands and fixtures post
through, since ~176 call sites post hand entries with no receipt. The
viewer's missing_receipt calls requirement; its string match is gone.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HS8WZziDnyxunC23MUxrLp
2026-10-29 13:55:17 -06:00
swaitsandClaude Fable 5.1 28d46332e4 feat(store): draft states and discards are journaled, posted_seq is kept (P4)
Accounting reason: a draft that waits on its receipt, and one thrown
away, are facts about the books' working papers that must survive a
restart and replay identically, or a discarded draft would come back and
hold its period open. entry.draft_state_set and entry.draft_discarded are
drafts_v2's (an older version stops at the declaration instead of
misreading them); no figure moves. Schema 19 (taken at merge as the next
after the current last): entries.posted_seq, and a draft_states table
(drafts are not rows of entries, so the plan's entries.draft_state column
has nowhere to live). posted_seq is the journal line that posted each
entry (entry.posted, entry.reversed, the line that closed a period or
posted a schedule month), identical live and on replay.

8.0.6: a hand-written line of each new shape in
books_written_under_older_rules_still_open; fixture old-books/p4_before
written by 25b52580 (hand entries posted late, a reversal, a document, a
schedule month, a recurring draft, open drafts, a close) opens to the
same figures (serialized entries omit a building state, so no golden
moved); drafts_v2_is_its_own_feature; the fuzzer and GUI journal know the
types.

Tests: draft_states_replay_and_project, drafts_v2_waits_for_the_upgrade,
posted_seq_is_the_posting_lines_seq, an_older_version_stops_at_the_draft_events.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HS8WZziDnyxunC23MUxrLp
2026-10-29 13:55:17 -06:00
swaitsandClaude Fable 5.1 be8bcf340e feat(drafts): a draft has a state and can be discarded (P4)
Accounting reason: an entry that is finished but waits on its receipt is
a different thing from one still being built, and a draft that will
never post must have a way out, or the period that holds it open can
never close (the Lean hypothesis hdrafts). JournalEntry gains
DraftState { Building, AwaitingEvidence { note } }; Ledger gains
set_draft_state (a note: no line, balance or report moves),
discard_draft (a draft only; a posted entry is never removed, B-8; ids
are never reused; what was recorded about the draft goes with it) and
posted_seq (the journal line that posted an entry, for the digest's
since_seq). Lean Step.discard with its preservation proof and three
theorems (a discard moves no figure, removes only its draft, and
unblocks a close), listed in Audit.lean.

Tests: discard_unblocks_close (ledger-core invariants).

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HS8WZziDnyxunC23MUxrLp
2026-10-29 13:55:17 -06:00
swaitsandClaude Fable 5.1 75882c5e07 merge: U0e, tokens in a file under the data root, the OS keyring removed (branch U0e, d8816f9e; D-63)
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:32:25 -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 d703b68911 refactor(host): tokens live in a file under the data root (U0e)
D-63: the 0700 data root is the trust boundary already (I-1: hidden, not
locked), so the OS keyring added no wall and cost headless servers a D-Bus
session, CI a knob and every OS its own store. The file keeper is the only
store: run/tokens/, 0600, written atomically. The three keyring stores, the
file_keyring knob and their tests are removed; an unreadable token file is an
error, never no token.

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 cfaf05b862 docs(plan): wave 1 landings; D-62 (Windows as P11c) and D-63 (tokens in a file, no keyring); units U0e and P11c
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
8.0.1 and the Status lines record U0d, S4.5, P13 steps 4-5 and P11a
step 0 as landed. Two owner decisions from today's questions: D-62,
the first bundled app ships for macOS and Linux and the native Windows
port is its own unit P11c (step 0 found it is a port: the host, the
MCP transport, the store's locking and permissions, the data root);
D-63, agents' tokens live in a 0600 file under the 0700 data root on
every OS and no OS keyring is used (the plan had never recorded the
owner's view, so step 0 built two more stores). Unit U0e removes the
three keyring stores and the knob; 3.3's keyring sentence and step 0's
are struck. 8.0.3 confirms the eight bookspec example ids P13 took.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HS8WZziDnyxunC23MUxrLp
2026-10-29 13:18:39 -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 ac0c949b9a merge: P4b S4.5, the issued PDF, the viewer's invoice details, the handbook (branch S4.5, ab3bd958)
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 ab3bd958c4 docs(plan): S4.5 as built (P4b S4.5)
Departures from the Build text, one bullet each: no golden sha to
recompute, invoice_document's dropped `today`, where the ways to pay, the
footer and the website come from, exact unit prices, the handbook steps
the earlier slices had already written, the viewer parity test's shape,
and the bookspec index left to P13.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HS8WZziDnyxunC23MUxrLp
2026-10-29 12:55:41 -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 df93d8b951 feat(viewer): the invoice page shows its number, terms, bill-to and PO (P4b S4.5)
The viewer reads the same invoice the server prints. The Money in list and
the page title name an invoice by its issued number (a legacy invoice keeps
"Invoice {id}"); a Details card shows the number, terms, the bill-to as it
was at issue (contact, address, phone, email), Ship-to, the customer's PO
and memo, and a bill's vendor number, day received, terms and memo. Save
invoice PDF still re-creates the current copy; when the issued copy is
filed (found through its link to the invoice, never by name) Save issued
copy and Open issued copy hand over those very bytes, checked against
their SHA-256. Nothing is written to the books.

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 400d0cdcab feat(pdf): the issued invoice carries what the customer pays from (P4b S4.5)
The page a customer pays from must show what the books issued, and only
that. InvoiceDoc gains each line's quantity and unit price ("3 x $33.33"
beside the line's own amount, never recomputed), the customer's PO, memo,
footer, the ways to pay (a QR code through labels::qr_svg for a method
with a link that asks for one), the seller's email and website, and a
Ship-to block. InvoiceVersion separates the issued copy (no stamp, no
settled rows, no "re-created" note) from the current copy, whose
"Re-created on" note prints the as_of it was given, never the wall clock,
so the bytes stay deterministic (B-40 item 3). Every amount reaches the
page through report::amount_parts; every string is data.

ledger_package builds all three copies (issued, DRAFT, current) from one
function over the invoice's own snapshots (A-13, D-55: number, terms,
bill-to contact block, shipping address), falling back to the party record
only for a document made before snapshots, so the three cannot disagree.
invoice_document drops its now-unused `today` argument.

rules.md gains B-39 (terms) and B-40 (numbers and the issued copy) from
the 8.3.13 spec blocks, CONVENTION until their sources are vendored.
No golden moved: the render test never held a hash, it compares two
renders for equal bytes, and still does.

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 3acd634dc4 feat(bookspec): GnuCash as the second engine, its differences settled (P13 step 5)
bookspec-gnucash implements the Engine trait over the oracle container
(oracle/gnucash/books.py) and claims the rules whose examples ask only
for balances. It passes BS-entry-3, BS-ledger-3, BS-statement-1 and
BS-statement-2 and differs from three examples, each adjudicated from the
sources and pinned by tests: GnuCash accepts an unbalanced entry and
parks the difference in an Imbalance account (BS-entry-1 refuses it); it
reads a credit of minus 100 as a debit (BS-entry-2 is a convention that
removes the ambiguity); it rounds 10.005 to 10.01 silently (BS-ledger-1
is the owner's choice not to). The example stands in each case: GnuCash
is a cross-check, not the authority.

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 0e6b6bb768 feat(bookspec): the checker derives the examples' figures from the rules (P13 step 5)
An engine-free reference model with exact decimals replays an example's
entries, reversals, close, bills and invoices and computes its
trial_balance, balances, profit_and_loss and entry figures from the
rules' symbolic forms (signed sums, normal sides, A = L + E + (R - X),
net income), and checks that each refusal an example expects is justified
by its rule (debits differ from credits, an amount is not positive, finer
than the minor unit, an entry already reversed, a date inside a closed
period). An example whose figure disagrees fails the specification's
build before any engine is consulted; what the model cannot derive is
reported as skipped, never as passed.

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 1fbc1383cb feat(bookspec): the specification text and an example for the ledger's own laws (P13 step 4)
bookspec/spec holds the first edition: the model and the five laws
(00-model.md) and one file per area, each B-rule a section with its
status, a MUST/MAY statement, sources pinned either to a vendored source
with a verbatim quotation or to an exact external paragraph, its symbolic
form, its examples and its applicability. B-39, B-40, B-43 and B-45 are
reserved sections (allocated, not yet stated in rules.md); B-44 is stated
in rules.md and now carries its bookspec id and CONVENTION status.

Eight new examples show the laws that had none, each figure worked by
hand: an unbalanced entry is refused (Pacioli ch. 36, Pub. 583), a
negative or zero line is refused, a fraction of a cent is refused (ISO
4217), a child account takes its parent's type, the trial balance and the
accounting equation on one small book (cash 6550.25 = loan 2000 + capital
5000 + (sales 750.50 - rent 1200.25)), a reversal leaves the original
standing and a second reversal is refused, a close sweeps revenue and
expenses into Retained Earnings (3999) and the closed quarter takes no
late entry, and cents add exactly. The refusal vocabulary gains
unbalanced, negative_amount, zero_amount, too_few_lines,
finer_than_minor_unit, parent_type_mismatch, already_reversed and
period_closed; a refused entry may carry a non-positive amount.

The checker's spec.rs fails the build on a rule without its section, a
statement without MUST/SHOULD/MAY, a pin that is not verbatim in its
vendored source, examples that differ from the catalogue, or more than 60
pages.

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 055decf7e2 feat(bookspec): the five laws as Lean theorems (P13 step 4)
The first edition's five laws (debits equal credits; the accounting
equation; the trial balance balances; net income articulates to equity;
the cash-flow sections sum to the change in cash) are theorems in
bookspec/lean, ported from verify/lean (Basic, Entry, Statements, Closing,
Ledger under namespace Bookspec) and restated by name in Laws.lean, each a
statement about every reachable state of the books. Audit.lean prints
their axioms; the checker's lean.rs test fails on a sorry, a law not
audited, or any axiom beyond propext, Quot.sound and Classical.choice.
Money is Int, the exact count of the smallest unit: every law is a law of
addition, so the model is faithful to exact decimals.

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 ce69d9d7bc build(mise): the packager tools are pinned when the trial recipe exists
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
P11a step 0 pinned cargo-packager, cargo-packager-updater and
cargo-generate-rpm for the trial it did not run. mise-action compiles
every pinned tool on every CI run of every job, so three cargo tools
nobody calls yet would cost minutes per run. They return to mise.toml
with the recipe that calls them (AGENTS.md: if a recipe needs a tool,
it goes in mise.toml), when the trial step starts.

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 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 39c76d3fac merge: U0d, the reader crate (branch U0d, bb1eb842; D-61)
check / check (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 12:45:54 -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 f98471516e docs(handoff): the worker's checks follow D-60
check / check (push) Canceled after 0s
two-user / two-user (push) Canceled after 0s
The worker instructions still said `just check` before every PR; under
D-60 a worker compiles every target, runs fmt, clippy on the crates
touched and the unit's named tests, and the EM runs the exhaustive
gate once per wave.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HS8WZziDnyxunC23MUxrLp
2026-10-29 12:29:45 -06:00
swaitsandClaude Fable 5.1 25b5258036 docs(plan): Q-9 answered (D-61): the reader crate, unit U0d after S4.5
check / check (push) Canceled after 0s
two-user / two-user (push) Canceled after 0s
The owner, 2026-10-29, the question tool: the reader (`extract.rs`)
moves into a small library crate of its own, `ledger-reader`, that
`ledger-store` and `ledger-import` both depend on, so the writer sheds
the typesetter and the crate map is layered again; half a day for the
implementing team once S4.5 is in. 8.2 gains the unit U0d in the
template (its Done-when is `cargo tree`), 8.0.1 its row, 8.0.3 the
crate name (D-53's closed `ledger-extract` subprocess stays closed),
10.1 D-61, 10.3 closes Q-9. STATUS.md, HANDOFF.md and architecture.md
say the same; the arrow stands as built until U0d lands.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HS8WZziDnyxunC23MUxrLp
2026-10-29 10:54:16 -06:00
swaitsandClaude Fable 5.1 1111157ed1 ci(check): room on the runner for the workspace's test binaries
The first `check` run after P6 landed (`bcd1048b`) died linking the
store's and the package's test binaries: "No space left on device",
ld killed by SIGBUS. The store's test closure now carries the file
reader (hayro, zip, quick-xml, and through ledger-import the Typst
typesetter: plan Q-9, D-61 moves the reader out after S4.5), and the
runner's disk had no room left for it with full debug info and
incremental caches.

The job drops incremental compilation (a fresh runner gains nothing
from it), keeps line tables only (file:line in a panic's backtrace at
a fraction of the size), removes the image's unused toolchains before
the build, and prints the disk before and after so the next squeeze
is visible.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HS8WZziDnyxunC23MUxrLp
2026-10-29 10:54:16 -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 3a1f4fd4eb test(store): a write-off whose notice is another entry's receipt is refused before posting
The defect found merging S7 and P6: under P6's dedup the notice's
bytes resolve to the record already on file, and the one-receipt-one-
entry rule refused it only after the write-off had journaled its
entry. The store now checks the deduplicated record before the journal
line (`6c26160e`'s companion fix in `write_off_invoice`); this test
pins it: the refusal names the record and its entry, the head and the
journal are unchanged, no entry posts, the invoice stays open, and the
books verify. Also formats the B-27 test `ci-fixes` brought in.

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 95a54d69ab merge: ci-fixes (fuzz_settings_replay's renamed path; a B-27 check for an unknown convention kind)
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HS8WZziDnyxunC23MUxrLp
2026-10-29 08:21:47 -06:00
swaitsandClaude Fable 5.1 bfd453b679 test: the tests P6's gate never reached, under one-record dedup
check / check (push) Canceled after 0s
two-user / two-user (push) Canceled after 0s
P6's branch paused with its gate interrupted, so five tests that
predate plan P6 step 4 (the same bytes filed twice are one attachment,
linked to each subject) still encoded the invariant it replaced, and
one golden carried counts from before extraction records existed.

- store: `blob_dedup_and_hash_verification` expects one attachment
  row under the first filing's name, not two. The `attach_receipt`
  spec block's INVARIANT 2/3 now say what P6 built; the stale "each
  filing is its own attachment" was the one wording left.
- store: the power-loss replay copies the blob store too. The books
  on disk were always journal, database and blobs; verify now reads
  an extraction's text blob (P6 step 2), so a copy without `blobs/`
  cannot verify.
- store: the refusals property allows `AttachReceipt` and
  `FileDocument` to journal nothing when the bytes are already on
  file: the record they return is the record they were.
- viewer: each statement the reconciliation helper files has its own
  bytes (a wrong statement is voided and a new one comes); identical
  bytes would be one record, and a record is one statement.
- viewer: the tall sidebar's shapes golden is re-blessed. The fixture
  books now carry extraction records, so the Overview attention
  count, the event count and the verified-file count the sidebar
  prints moved; no layout changed.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HS8WZziDnyxunC23MUxrLp
2026-10-29 07:04:22 -06:00
swaitsandClaude Fable 5.1 6c26160e26 test(bookspec): each write-off in an example files its own notice
Under P6's dedup (one record per bytes) the adapter's constant letter
made the example's second write-off of the same invoice reuse the first
write-off's record, which a receipt's one-entry rule (B-38) refuses, and
the write-off now refuses that before anything is journaled (the S7/P6
integration fix). The example's two write-offs have two notices, which
is what it describes.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HS8WZziDnyxunC23MUxrLp
2026-10-29 05:25:22 -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 fb83af5948 verify(lean): one docstring on Books after the S5 and S7 merges
check / check (push) Canceled after 0s
two-user / two-user (push) Canceled after 0s
The S7 merge kept both branches' doc comments for the Books structure,
S5's (which names the sends) stranded above an unrelated theorem and
S7's on the structure; Lean refused the stray one. S5's text stays, on
the structure it describes. Kani's 52 harnesses verified in the same
run; only Lean's build stopped.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HS8WZziDnyxunC23MUxrLp
2026-10-29 05:02:12 -06:00
swaitsandClaude Fable 5.1 0cbb49b6d2 test(fuzz): the replay fuzzer writes invoice.written_off
S7 added the event type (R-B step 6: the fuzzer's list must name every
type the store replays, the_replay_fuzzer_writes_every_event_type) and
the merge lost the line; the S7 gate on main caught it.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HS8WZziDnyxunC23MUxrLp
2026-10-29 03:54:10 -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