Commit Graph
552 Commits
Author SHA1 Message Date
swaitsandClaude Fable 5.1 74f1691f77 fix(store): a root too deep for a socket's address is served on macOS too
portable / portable (macos-latest) (push) Waiting to run
portable / portable (windows-latest) (push) Waiting to run
check / check (push) Canceled after 0s
two-user / two-user (push) Canceled after 0s
A Unix socket's address holds about 104 bytes. Linux reached a deep
root's sockets through /proc/self/fd; every other Unix refused the
root ("too long for its sockets"), which the portable macOS job met
with a test home under the runner's temp folder, and a Mac with a deep
home would meet too. On those systems the socket path now goes through
a short link of this account's, /tmp/wanderbooks-<uid>/<hash of the
dir>, in a folder made 0700 and refused unless it is this account's
own; a stale or foreign link in its place is replaced or refused. The
server and its clients compute the same link, so nothing else changes.

Tests (tests/socket_path.rs, every Unix): a deep root gets a path that
fits and reaches the dir; the link is private, stable, and replaces a
stale one of ours.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HS8WZziDnyxunC23MUxrLp
2026-10-29 18:09:00 -06:00
swaitsandClaude Fable 5.1 1da01c3123 test(cli): the spec script's package export gives the audit override; macOS tests keep short paths
check / check (push) Canceled after 0s
portable / portable (macos-latest) (push) Canceled after 0s
portable / portable (windows-latest) (push) Canceled after 0s
two-user / two-user (push) Canceled after 0s
GitHub's check on 62d42b94: the spec script now runs `books.audit`
(P8), so the package gate is live when the script exports and the
run is behind the head; four tests failed on `audit_required`. The
script gives `without_audit_reason`, as a human would.

The portable macOS job, with the server's log in hand: "the books'
private folder's path is too long for its sockets". A Unix socket path
holds 104 bytes; the runner's temp folder plus a test home plus
`Library/Application Support/wanderbooks/run/` is longer. The job sets
TMPDIR=/tmp, so every test home is short there.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HS8WZziDnyxunC23MUxrLp
2026-10-29 17:49:11 -06:00
swaitsandClaude Fable 5.1 db7910f937 ci: the portable macOS and Windows jobs run as their own workflow
GitHub serves a job's log only when its whole run has completed, and
the Linux `check` takes over an hour, so a macOS failure's reason was
unreadable for that long while the portable jobs sat in the same run.
Each OS's run now completes on its own; the jobs are unchanged.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HS8WZziDnyxunC23MUxrLp
2026-10-29 17:37:39 -06:00
swaitsandClaude Fable 5.1 62d42b9458 test(cli): every test asks the platform where the data root is
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
The four other test files that spelled out `.local/share/wanderbooks`
(setup, books_home, format_upgrade, backups) use `default_root` and
`platform_data_dir`, so they hold on macOS as on Linux.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HS8WZziDnyxunC23MUxrLp
2026-10-29 17:22:37 -06:00
swaitsandClaude Fable 5.1 2a6bac02d5 test(cli): the IIF warning count ignores P8's audit notice; the harness asks the platform where the root is
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 880278ea: `cpa_package_holds_the_year_and_its_evidence`
counts the IIF transliteration warnings, and P8's "No counted audit
stands behind this package" notice (a warning while the books hold no
audit run) made four into five; the count now excludes it.

The portable macOS job, now that the harness prints the server's log:
"there is no books' private folder yet: run `wanderbooks setup`
first". `Home::root()` spelled out `.local/share/wanderbooks`, the
Linux data folder; `setup --home` on macOS makes the root under
`Library/Application Support`. The harness asks `default_root` now,
the same answer on every OS.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HS8WZziDnyxunC23MUxrLp
2026-10-29 17:21:43 -06:00
swaitsandClaude Fable 5.1 2774680d65 docs(plan): P11b and P13 step 6 landed; D-65 amended (Gitea only); the night's status
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HS8WZziDnyxunC23MUxrLp
2026-10-29 17:11:37 -06:00
swaitsandClaude Fable 5.1 f625848acb merge: P11b, the agent dock, books homes, the first-run page (branch P11b, abcb2d6b; D-27)
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HS8WZziDnyxunC23MUxrLp
2026-10-29 17:10:58 -06:00
swaitsandClaude Fable 5.1 abcb2d6b27 docs(plan): P11b as built (P11b)
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 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 0442ab804e feat(host): books homes and starting an agent in one (P11b)
D-27: each book gets a visible folder, <Documents>/Wanderbooks/<id>, where an
agent works. It holds AGENTS.md and CLAUDE.md (three lines), the project-level
.mcp.json entry (wanderbooks mcp --book ID) and .claude/settings.json allowing
mcp__wanderbooks-ID__*, and nothing of the books: no data, key, token or path
into the data root (invariant I-1 stays: the books are hidden, not here). The
two JSON files are merged into and the text files written only when absent, so
a person's edits stay. Named by the book id, which never changes, not by the
company name, which is set after the books are made.

The viewer's New books... and wanderbooks connect make the home; connect_agent
is unchanged. start_agent says how to run Claude Code or Codex in it, or hands
back the vendor's installer (never bundled). Nothing is marked trusted for any
agent (Q-7: recommended answer, let Codex ask). First-run helpers (state, set
up the private folder, first owner passphrase) let the viewer onboard with no
terminal.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HS8WZziDnyxunC23MUxrLp
2026-10-29 17:06:08 -06:00
swaitsandClaude Fable 5.1 880278ea0e fix(mcp): a close still offers books.verify; the test harness says why a server didn't start
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 794807ab: P8's fit-up replaced the step after
`books.close_period` (`books.verify`) with the audit and the
attestation, and P5's month_end_close eval, which follows only what
the books offer, lost the step it follows. The close now offers the
chain check first, then the audit the attestation rests on.

The portable macOS job failed every over-the-wire test with "the
server didn't say ready" and nothing else: the harness sent the
server's stderr to a file in the temp home and never showed it. It
now panics with that log, so the next run says what macOS refused.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HS8WZziDnyxunC23MUxrLp
2026-10-29 16:53:56 -06:00
swaitsandClaude Fable 5.1 d738c445cc merge: P13 step 6, the specification lives at git.swaits.com/swaits/bookspec, pinned (branch P13, b77a25fd; D-65)
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HS8WZziDnyxunC23MUxrLp
2026-10-29 16:52:09 -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 794807ab90 docs(plan): P10 landed, P11a through step 4 and the macOS job; D-64 (signing deferred), D-65 (bookspec public repository)
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 16:29:21 -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 5d49fe8f0d merge: P10, the re-audit runbook and its CI mirror (branch P10, 7fe8570a)
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 310fa105a6 docs(plan): P11a step 4, the AppImage restart and the macOS bundle as built
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HS8WZziDnyxunC23MUxrLp
2026-10-29 16:25:35 -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 6e7deb15c7 feat(host): the daily update check and the staged update (P11a step 4)
While the books are open the host looks at the release feed at most once
a day (knob update_check, noted in updates/checked so a restart does not
look again), and only downloads and verifies a newer release into
<root>/updates/staged/ (sha256 AND minisign signature, as for update).
It never swaps: the staged program is applied (a) by an opener just
before it starts a host, when no host runs, so no call is cut off, after
a second check against the key (a staged file that fails or does not
start is dropped), and (b) by wanderbooks update, the owner's say-so,
which reuses the staged copy instead of downloading again. A local
release can be named in <root>/updates/feed (file:// only, with its key
file, same rule as --trusted-key). The program's version is set by main
since packages rewrite only the cli's. No new dependency.

Tests (crates/ledger-cli/tests/update.rs): the_host_stages_a_daily_update_and_the_next_start_applies_it,
a_daily_update_that_is_not_ours_is_not_staged,
update_installs_the_copy_the_host_staged_without_downloading_again,
a_staged_update_that_was_tampered_with_is_dropped_not_applied.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HS8WZziDnyxunC23MUxrLp
2026-10-29 16:23:40 -06:00
swaitsandClaude Fable 5.1 7fe8570acd docs(plan): P10 as built
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 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 a2f013955c docs(audit): the re-audit runbook (P10)
A re-audit is only worth its name if every figure is traced and every
receipt and entry carries an attestation that counts, so the runbook drives
the audit tools in the order the books need: audit, correct by reversal
(never edit), attest at the bar, attest the periods, then compare both CPA
packages and sign off in the journal. D-12's evidence link uses
link_attachment: the books have no inherit_evidence tool.

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 562b893c64 feat(host): an AppImage's host learns its image and restarts onto a replaced one (P11a step 3 gap)
An AppImage runs the program from a mount that goes away with the image
it came from, so a host held open past an update kept running the old
mount and never noticed the replaced image. The host now reads APPIMAGE
(added to its environment allow-list only when it names an absolute
file; it is a path the host reads about itself and never tells an agent,
so I-2 holds), runs and restarts from that file, and tells a replaced
program by its file's inode, size and last write. Test:
a_replaced_appimage_restarts_the_books (the programs run from one file
while APPIMAGE names another, as in a mount).

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HS8WZziDnyxunC23MUxrLp
2026-10-29 16:15:11 -06:00
swaitsandClaude Fable 5.1 5ecc01b682 docs(plan): P5 and P8 landed; P11a through step 3
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 16:05:30 -06:00
swaitsandClaude Fable 5.1 3ee2020bb2 merge: P8, audit and attestations: the checks, journaled runs and attestations, the workpaper PDF, the CPA gate (branch P8; schema 20)
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HS8WZziDnyxunC23MUxrLp
2026-10-29 16:05:30 -06:00
swaitsandClaude Fable 5.1 4774bd9abd feat(audit): fit the audit tools into the agent surface (P8)
After P5 merged first: a close leads to the audit and the period's
attestation, an audit run to its workpaper and the claims behind it, the
attest tools to the attestations and the CPA package; a refusal for want of
a standing audit is the guard audit_required with books.audit as its
remedy; the audit prompt runs the audit tools. policy.cpa_package_requires_audit,
unset, refuses once the books hold an audit run and warns before, so a
book that has not begun the programme is not stopped from its first handoff
by a rule it has not met; set to refuse, it holds from the start.
bookspec states B-45 (convention) in its checker.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HS8WZziDnyxunC23MUxrLp
2026-10-29 16:03:53 -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 d45e842873 feat(workpaper): the audit workpaper as a deterministic PDF (P8)
One page set a CPA can read: what was checked, through which day and state
of the books, every finding with its code and severity. No clock and no
random id, so the same findings give the same bytes.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HS8WZziDnyxunC23MUxrLp
2026-10-29 15:54:08 -06:00
swaitsandClaude Fable 5.1 6b7dccd1ee feat(audit): audit runs and attestations are journaled (P8)
A claim that someone checked the books is evidence the CPA handoff rests on,
so it is a journal fact and replays: audit.run (a programme run through a
day at the books' figure head, with the hashes that make it reproducible and
the two filed workpapers), receipt.attested, entry.reviewed and
period.attested. Behind the journal feature audits, schema 20; no figure
moves. The figure head is the last line that bears on a figure: filing a
workpaper, a run or an attestation never makes an audit stale, an entry,
document, close, settings or role change does, so a period is attested only
on an audit that still describes the books. A receipt reading that differs
from the entry is recorded as disagreeing and never counts; whether a claim
counts is derived against the bar when asked, never stored.

The store also reads what a ledger cannot: the chain, the database against
a replay, stored and extracted files, the access log, duplicate files, and
the backups' standing handed in by the server. Fixture old-books/p8_before
was written by the version before this one and opens to its figures.
period.attested was the tests' placeholder for an unknown event type; they
use ledger.not_yet_written now.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HS8WZziDnyxunC23MUxrLp
2026-10-29 15:54:08 -06:00
swaitsandClaude Fable 5.1 843bcc421f feat(audit): the statements checks, the bar and the audit settings (P8)
An audit is the books read as an auditor reads them, through a day. The
pure checks over a ledger are one function per finding code in
ledger_core::audit: the trial balance balances, assets equal liabilities
plus equity, the P&L articulates to equity (net income since the first
entry is current earnings plus what the closes moved), the cash flow
foots, the control accounts tie to their documents, a members close stays
within a minor unit of each member's exact share, schedules and
depreciation are posted, the register holds the cost accounts' cost,
every posted entry meets evidence::requirement (a reversal resting on its
original's receipt is listed as a note, not a failure), no draft is open,
every revenue and expense account has a tax category, and the 1099 and
settings problems the caller reads are findings. A check that cannot be
made is itself a finding. Findings come in one fixed order, so the same
books give the same list. The caller hands in what a ledger cannot know:
the receipts filed, the cash flow's footing, the settings problems.

The attestation bar (D-9) is a value, not a table: reasoning effort high,
two verifier agents and an adversarial check, with counts() deciding
whether a claim meets it. Every claim needs the effort; one on a
year-level audit needs the whole bar; a reading that disagrees with the
books never counts. policy.attestation_bar (a new Kind::AttestationBar,
one canonical text) and policy.cpa_package_requires_audit are the two
settings; both wait for the journal feature audits.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HS8WZziDnyxunC23MUxrLp
2026-10-29 15:53:35 -06:00
swaitsandClaude Fable 5.1 d9d63b16dc merge: P5, the agent surface complete: the envelope, source corrections, journal history, prompts, books.start v2, evals (branch P5, d43a7b4e)
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:52:45 -06:00
swaitsandClaude Fable 5.1 d43a7b4e1b test: a preview's confirm step repeats the arguments, so id and tag previews differ there (P5)
dispose_asset_takes_the_tag_and_the_tag_stays_reserved compares the preview
a disposal gives by asset id and by tag and says they show the same, apart
from the digest, which names the arguments as sent. The envelope's confirm
step repeats the arguments as sent too, so next_steps is set aside beside
the digest; everything the preview shows is still compared.

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 ad1afb478d fix(mcp): a read says only what follows; a success adds no empty warnings (P5)
Two existing contracts the envelope broke when every result got every field:
a resource and its tool say the same (books://settings is books.settings,
checked by the_kept_resources_read), and a tool with no warnings has no
warnings key (books.add_account, defect 8). So head and next_steps are on
every tool that can change the books, next_steps is on a read only when
something follows it, guards is on a refusal, and the envelope adds no
warnings of its own (a tool's stay as they are).

The happy-path spec test now exercises books.journal_history (it fails on
a tool it never called), the demo company gets its books.start size check
(Done when 3), and the As-built block of P5 is written.

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 8e683942a2 test: the alias sweep reaches the two tests that built their arguments in a closure and a loop (P5)
projects_parity built both its invoices and its bill through one closure that
passed party_id, and spec_projects looped over (invoice, bill) with one
spelling; with the aliases gone each names customer or vendor as its tool
takes it.

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 632938fa97 style: rustfmt and one clippy lint over the P5 files
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 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 a319a64c3a refactor(mcp): the party_id, document_id and cash_account aliases are removed (P5)
Agent reason: one name for one thing. S4 and S6 kept the old spellings "until
P5" so an agent that learned them did not break; P5 removes them, so the
schema an agent reads has one parameter for each fact and a call cannot name
the same thing twice with different answers.

books.create_invoice takes customer and books.create_bill vendor (a record's
id, its party's id or its name; required), no party_id.
books.receive_payment and books.pay_bill take invoice or bill and
deposit_to or paid_from, no document_id or cash_account; a payment names its
document and its account or is refused saying which to pass. A number that is
one record's id and another's name is still refused, never guessed (INVARIANT
6).

The over-the-wire tests that used the old spellings now use the new
(document_id becomes invoice or bill, cash_account becomes deposit_to or
paid_from, party_id becomes customer or vendor given as the id's text); the
one test that named the alias, party_id_alias_still_works, is now
a_customer_by_id_and_by_name_is_one_record and checks the same two spellings
of a customer, by id and by name, name one record.

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 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