P3: the books' format moves only with the owner's go-ahead (D-39)
Books an update could write a newer format for open on their old format and are asked at every open; anything needing a new feature is refused until the owner agrees with the passphrase, after a verified backup that includes the keys. New books start on the current format. Each projection schema has its own database file, so an older version still opens the books until the owner agrees. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01T3gXMVt7XJPi6ens1or9Dj
This commit is contained in:
62 files changed
+5309
-628
No files matched your search
+48
-15
@@ -129,8 +129,13 @@ settings-and-conventions section says how to use them.
|
||||
state ("needs_onboarding" or "active"), settings_source, rules: {
|
||||
evidence, parties, approval_rules }, settings_summary: [{ key, value,
|
||||
set_by, seq }], conventions: [{ id, kind, title, summary }], open_items:
|
||||
[{ id, title, due }], truncated, handbook_sections, next_steps: [{ tool,
|
||||
args?, why }], isolation }`. The connect text names it as the first call.
|
||||
[{ id, title, due }], truncated, handbook_sections, format_upgrade?: {
|
||||
guard, format, adds, how }, next_steps: [{ tool, args?, why }],
|
||||
isolation }`. The connect text names it as the first call.
|
||||
`format_upgrade` is there only for books waiting for a format upgrade
|
||||
(below); its next step comes first, before anything else, at every
|
||||
start until it is done: `books.handbook {section: "format-upgrade"}`,
|
||||
"Tell the human first: …".
|
||||
`isolation` is `same_user (advisory)` (D-32): the books are hidden from
|
||||
agents in the owner's own account, not locked against them. Lists cut
|
||||
to fit say so in `truncated` ("conventions: 4 more (books.conventions)").
|
||||
@@ -138,6 +143,25 @@ settings-and-conventions section says how to use them.
|
||||
company's close steps, `audit` `books.verify`. Not built yet (P5):
|
||||
`book.id`, `book.url`, `head.last_handoff`, the `agent` input, and
|
||||
loading it up front under tool search. Also `books://start`.
|
||||
**The format upgrade** (plan 3.9, D-39, rules.md B-27). Books an older
|
||||
wanderbooks wrote are in an older journal format: they are served on it,
|
||||
and every tool that needs nothing new works as before. A tool whose
|
||||
change needs the newer format (on main: settings, conventions, decisions,
|
||||
open items and approvals need journal format 2; later formats add
|
||||
features) is refused before anything is journaled, its error beginning
|
||||
`format_upgrade_pending:` and saying that only the owner can give the
|
||||
go-ahead, with the owner passphrase, in the app or with `wanderbooks
|
||||
books upgrade ID` at a terminal, and that no tool can. No tool consents:
|
||||
none is offered, and the server takes the go-ahead only on its admin
|
||||
socket, from the host's lease intent `consent_format_upgrade` (the app's
|
||||
dialog, `books upgrade`), with the passphrase it checks. Before the
|
||||
upgrade the server commits the books to their own backup repository and
|
||||
proves the copy holds them exactly (journal, stored files, vault, keys);
|
||||
then one `journal.format_set` declares the whole format, and the same
|
||||
session's next call can make the change. Books this version creates are
|
||||
in its format from their second line, and an adoption's copy moves to it
|
||||
as it is made.
|
||||
|
||||
- `books.conventions { kind?, include_retired? }` → every convention with
|
||||
its full text, structure, revision and source (`books://conventions`).
|
||||
`books.set_convention { id, kind, title, body_md, structured?,
|
||||
@@ -746,11 +770,14 @@ through the policy.
|
||||
## Tools — integrity
|
||||
|
||||
- `books.verify { blobs? }` → `{ status: "ok" | "healed", events, head,
|
||||
healed?, blobs_verified?, checked }`. Verifies the journal's hash chain,
|
||||
healed?, blobs_verified?, database, other_databases?, checked }`
|
||||
(`database`: the projection file verified, one for each schema,
|
||||
`wanderbooks.v{N}.db`; `other_databases`: other schemas' files beside it,
|
||||
left for the versions that use them). Verifies the journal's hash chain,
|
||||
replays it against the live ledger, and compares every table of the
|
||||
SQLite database row for row against a fresh projection of the journal.
|
||||
A drifted database is rebuilt from the journal (the old file kept as
|
||||
`wanderbooks.mismatch.<time>`). With `blobs` (default true), re-hashes
|
||||
`wanderbooks.v{N}.mismatch.<time>`). With `blobs` (default true), re-hashes
|
||||
every stored file. The server runs the same check, and heals the same
|
||||
way, every time it starts.
|
||||
|
||||
@@ -906,8 +933,9 @@ first entry's).
|
||||
year (and its entries) whose payments the change reaches in a closed
|
||||
period or after that year's 1099s were due (January 31 of the next
|
||||
year): tell the human, a filed 1099 may need correcting. Journaled as
|
||||
`vendor.1099_set` (a W-9 flag alone as `vendor.w9_flag_set`), after a
|
||||
`journal.format_set` declaring the `vendor_1099_decisions` feature once.
|
||||
`vendor.1099_set` (a W-9 flag alone as `vendor.w9_flag_set`), under the
|
||||
journal feature `vendor_1099_decisions` (books an older version wrote
|
||||
refuse it, `format_upgrade_pending`, until their format upgrade: B-27).
|
||||
- `books.add_instrument { kind, label, last4?, owner_party?, account }` →
|
||||
`{ id, kind, card_or_network, label, last4?, owner_party?, account_id,
|
||||
account, warnings }`. `kind`: `bank` (the company's checks, ACH, wires,
|
||||
@@ -1120,10 +1148,12 @@ the name; `roles` include vendor and customer when it has those records.
|
||||
party itself on: the card it issued, the loan it made, what the members
|
||||
advanced (each member). `books.update_party { creditor_of: [codes] }`
|
||||
replaces the list (`[]` clears it), journaled as `party.creditor_set {id,
|
||||
account_id, on}`, one line per account added or dropped (the journal's
|
||||
feature `party_creditors` is declared before the first); refused,
|
||||
journaling nothing, for an account that isn't a liability, is a
|
||||
placeholder, or is payables or a payroll withholding (owed to many).
|
||||
account_id, on}`, one line per account added or dropped, under the
|
||||
journal feature `party_creditors` (books an older version wrote refuse
|
||||
the whole change, `format_upgrade_pending`, until their format upgrade:
|
||||
B-27); refused, journaling nothing, for an account that isn't a
|
||||
liability, is a placeholder, or is payables or a payroll withholding
|
||||
(owed to many).
|
||||
|
||||
**Each party's part of an entry** (`books.get_entry`, the
|
||||
`books.post_entry` preview, `books.set_entry_party`: `parties: [{ party_id,
|
||||
@@ -1569,9 +1599,10 @@ posting.
|
||||
one that isn't one line of at most 200 characters (kept trimmed); a
|
||||
`reason` is one to 2,000 characters with no full TIN. Journaled as
|
||||
`account.renamed { account_id, code, name, previous, reason }`, keyed
|
||||
by id (a code can change; an id can't); the first rename declares the
|
||||
journal feature `account_renamed` (rules.md B-27), so an earlier
|
||||
version stops at the declaration. `notes` always says that QuickBooks
|
||||
by id (a code can change; an id can't), under the journal feature
|
||||
`account_renamed` (rules.md B-27; books an older version wrote refuse
|
||||
it, `format_upgrade_pending`, until their format upgrade), so an
|
||||
earlier version stops at the declaration. `notes` always says that QuickBooks
|
||||
matches accounts by name (tell a CPA who imported an earlier IIF), says
|
||||
when sub-accounts' full names change with it, when the new name moves
|
||||
the account into or out of the cash accounts (`books.cash_accounts`
|
||||
@@ -1600,8 +1631,10 @@ posting.
|
||||
- `policy.require_tax_category` (`books.set_setting`): `warn` (unset) or
|
||||
`refuse` (rules.md B-32). The accounts invoices, bills and payroll
|
||||
create (Revenue 4000, General Expenses 6999, 6100, 6110) are never
|
||||
refused; their results' `warnings` name them. The first change naming
|
||||
the key declares the journal feature `require_tax_category`.
|
||||
refused; their results' `warnings` name them. A change naming the key
|
||||
needs the journal feature `require_tax_category`: books an older
|
||||
version wrote refuse it (`format_upgrade_pending`) until the owner's
|
||||
format upgrade (rules.md B-27).
|
||||
|
||||
## Tools — import
|
||||
|
||||
|
||||
@@ -133,8 +133,12 @@ the MCP server's reads, and the future GUI all hit SQLite — and SQLite is
|
||||
**disposable by design**: `rebuild` drops it and replays the journal.
|
||||
Suspected corruption is fixed by rebuild, never by forensics.
|
||||
|
||||
- **Location.** `$WANDERBOOKS_DATA/wanderbooks.db`, falling back to
|
||||
`~/.local/share/wanderbooks/wanderbooks.db`. The journal lives next to it
|
||||
- **Location.** `$WANDERBOOKS_DATA/wanderbooks.v{N}.db`, falling back to
|
||||
`~/.local/share/wanderbooks/wanderbooks.v{N}.db`, N the schema: each
|
||||
schema keeps a file of its own, and a version never migrates, writes or
|
||||
deletes another schema's (`ledger_store::db_file_for`; the legacy
|
||||
`wanderbooks.db` is used only while it holds this version's schema;
|
||||
rules.md B-27). The journal lives next to it
|
||||
at `$WANDERBOOKS_DATA/journal.jsonl`; blobs under
|
||||
`$WANDERBOOKS_DATA/blobs/`. WAL mode on, `synchronous=FULL`,
|
||||
`integrity_check` on startup.
|
||||
|
||||
+106
-35
@@ -331,7 +331,7 @@ earnings the same running way, D-38, 2026-10-20)
|
||||
`Ledger::members_regime`, `Regime::allocate`,
|
||||
`periods::allocate_cumulative`, `kernel::apportion_cumulative`,
|
||||
`periods::allocate`, `kernel::apportion`, `members_equity`,
|
||||
`Store::close_period_to` (the feature declaration), the store's
|
||||
`Store::close_period_to` (the feature it needs, B-27), the store's
|
||||
`close_to_from_journal`. **Checked:** Kani `apportion_is_exact`,
|
||||
`apportion_is_fair`, `apportion_cumulative_is_exact`,
|
||||
`cumulative_close_never_drifts_a_unit_at_halves`, `_at_tenths`,
|
||||
@@ -860,7 +860,7 @@ B-16 cites them (P3 depreciation review R7).
|
||||
`a_reversal_dated_before_the_entry_it_reverses_is_refused`,
|
||||
`a_schedule_month_reversed_after_it_ended_is_left_for_a_human`,
|
||||
`a_month_that_rounds_to_nothing_is_never_a_posting`,
|
||||
`a_schedule_month_posted_again_declares_month_reposting_and_replays`,
|
||||
`a_schedule_month_posted_again_waits_for_month_reposting_and_replays`,
|
||||
`daily_plans_pin_month_end_clamping`,
|
||||
`prop_daily_plan_sums_exactly_and_lands_on_month_ends`,
|
||||
`prop_monthly_even_plan_is_unchanged`; `ledger-mcp`
|
||||
@@ -1366,7 +1366,9 @@ unit tests in `tin_files.rs`.
|
||||
**Status:** DECISION (the owner, 2026-10-16; `plan/served.md` 5.1 and P0);
|
||||
DECISION (P0 review, 2026-10-16: a line's own hash is checked before the
|
||||
line is called newer; the envelope and its hash are frozen across
|
||||
formats)
|
||||
formats); DECISION (the owner, 2026-10-20, D-39, and `plan/served.md`
|
||||
3.9: older books move to a newer format only with the owner's go-ahead,
|
||||
asked at every open until given)
|
||||
|
||||
Every journal line has a type, and a version of wanderbooks reads a fixed
|
||||
set of them (`ledger_store::format::EVENT_TYPES`). A newer version may
|
||||
@@ -1410,19 +1412,14 @@ lines before it pass, so an older version never alters newer books.
|
||||
A line an earlier version can't read is announced by a journal feature
|
||||
(`ledger_store::format::KNOWN_FEATURES`): a new event type, or a field or
|
||||
value on an event type an earlier version knows but would misread. The
|
||||
writer declares it once, in the `journal.format_set` just before the
|
||||
first line that needs it, with the format in force (at least 2) and the
|
||||
features already in force (`Store::ensure_feature`): an earlier reader
|
||||
stops at the declaration, and books that never use the feature stay
|
||||
readable by it. A tool journaling several commands as one declares the
|
||||
feature before the first of them, never between them (a refused format
|
||||
upgrade would leave the tool's work half done): `books.set_w9` declares
|
||||
`vendor_1099_decisions` before the W-9 when the vendor's W-9 flag follows
|
||||
it. P3's: `members_cumulative` (B-13: a members close counted from its
|
||||
count's first close, `"rounding": "cumulative_since"`), `account_renamed`
|
||||
(before the first `account.renamed`, B-31), `require_tax_category`
|
||||
(before the first `settings.set` naming `policy.require_tax_category`,
|
||||
B-32), `party_creditors` (`party.creditor_set`: defect 24, B-35),
|
||||
books declare it in a `journal.format_set` before the first line that
|
||||
needs it, so an earlier reader stops at the declaration; this version
|
||||
declares every feature it knows at once (below), never one at a time on
|
||||
the way. P3's: `members_cumulative` (B-13: a members close counted from
|
||||
its count's first close, `"rounding": "cumulative_since"`),
|
||||
`account_renamed` (an `account.renamed`, B-31), `require_tax_category` (a
|
||||
`settings.set` naming `policy.require_tax_category`, B-32),
|
||||
`party_creditors` (`party.creditor_set`: defect 24, B-35),
|
||||
`payment_instruments` (`instrument.added`, `entry.instrument_set`:
|
||||
defect 12, B-19), `vendor_1099_decisions` (`vendor.1099_set` with who
|
||||
decided, a `not_reportable` decision, its reason or the date it takes
|
||||
@@ -1432,21 +1429,75 @@ effect, and `vendor.w9_flag_set`: defects 18 and 23, D-40, B-19),
|
||||
B-16) and `month_reposting` (a schedule month or a depreciation period
|
||||
posted again after a reversal un-posted it; an older version would refuse
|
||||
the second `schedule.posted`, and its projection ignore the second
|
||||
`asset.depreciation_posted`, B-15). A value
|
||||
in a line of a known type that only a newer version writes (a 1099
|
||||
decision or reason kind, a payment method's kind) is newer, never
|
||||
corrupt. A command decides whether it needs a feature, and declares it,
|
||||
before its first journaled line, so a feature refused (a format upgrade
|
||||
awaiting the owner's consent) journals nothing, never an entry without
|
||||
the line that says what it is
|
||||
(`a_refused_journal_feature_journals_nothing`). A later piece asks the
|
||||
owner's go-ahead before the first such declaration (plan D-39).
|
||||
`asset.depreciation_posted`, B-15). A command asks for the feature it
|
||||
needs (`Store::ensure_feature`) before its first journaled line, and only
|
||||
when the line it is about to write uses it, so a write refused for
|
||||
waiting on the upgrade journals nothing, never an entry without the line
|
||||
that says what it is (`a_write_waiting_for_the_upgrade_journals_nothing`),
|
||||
and a write that needs nothing new is never refused. A tool journaling
|
||||
several commands as one asks before the first of them, never between
|
||||
them (a refusal there would leave the tool's work half done):
|
||||
`books.set_w9` asks for `vendor_1099_decisions` before the W-9 when the
|
||||
vendor's W-9 flag follows it. A value in a line of a known type that only
|
||||
a newer version writes (a 1099 decision or reason kind, a payment
|
||||
method's kind) is newer, never corrupt.
|
||||
|
||||
**The other way round: the format upgrade.** This version's format is
|
||||
the newest it reads with every feature it knows (`format::current`). Books
|
||||
it creates declare it in their second line, so they wait for nothing.
|
||||
Books an older version wrote declare less (format 1, written before the
|
||||
gate, declares nothing): they *wait for a format upgrade*, which is a fact
|
||||
of their journal alone (`JournalFormat::upgrade`). They open and replay as
|
||||
they are, byte for byte; everything that needs nothing new is written as
|
||||
ever; a write that would need the newer format or a feature they haven't
|
||||
declared is refused before anything is journaled
|
||||
(`StoreError::FormatUpgradePending`, guard `format_upgrade_pending`), never
|
||||
declared on the way. Only the owner's go-ahead moves them, with the owner
|
||||
passphrase (the server checks it, Argon2id): in the app, which asks at
|
||||
every open until it is given, or with `wanderbooks books upgrade ID` at a
|
||||
terminal, whose commands that open the books also ask; never an agent, and
|
||||
no tool. Before the upgrade the server commits the books to their own
|
||||
backup repository and proves the copy holds them exactly as they are
|
||||
(journal, stored files, vault and keys, byte for byte), all under the
|
||||
books' lock; a backup that can't be made or proven upgrades nothing. The
|
||||
upgrade is then one `journal.format_set` declaring the whole format, so a
|
||||
book's format moves exactly once per version; books already in it are left
|
||||
alone. An adoption's copy is new books of this version and moves to its
|
||||
format as it is made (the original keeps its own). The upgrade never
|
||||
weakens the gate: books declaring a feature this version doesn't know are
|
||||
newer, as above.
|
||||
|
||||
**Each projection schema in a file of its own** (the lead's decision,
|
||||
2026-10-21, for 3.9's "before consent, the way back is always there"). The
|
||||
books' SQLite projection is derived from the journal, and its schema moves
|
||||
with the version (`schema::SCHEMA_VERSION`), but an older version refuses a
|
||||
database of a newer schema (`SchemaTooNew`). So each schema keeps its own
|
||||
file, `wanderbooks.v{N}.db` (`store::db_file_for`): a version opens or
|
||||
creates only its own; its own missing, it is built from the journal; the
|
||||
legacy `wanderbooks.db` (every version through P2, schema 12) is used only
|
||||
while it already holds this version's schema, so books needing no
|
||||
migration aren't rebuilt for nothing; a file of any other schema, older or
|
||||
newer, is never migrated, written, refused or deleted, and stays byte for
|
||||
byte for the version that wrote it (going back finds it as it left it, or
|
||||
builds its own). A file named for this version's schema that holds another
|
||||
is damaged, kept aside and rebuilt, as a corrupt one is. `books.verify`
|
||||
(`database`, `other_databases`) and `wanderbooks doctor` say which file is
|
||||
in use and which others are there; none is deleted for being stale.
|
||||
|
||||
**Enforced:** `ledger_store::format` (`JournalFormat::admit`, `declared`,
|
||||
`gate`, `settle`, `declaration`), `journal::ENVELOPE_KEYS`,
|
||||
`gate`, `settle`, `declaration`, `current`, `JournalFormat::upgrade`,
|
||||
`require_format`, `require_feature`), `journal::ENVELOPE_KEYS`,
|
||||
`JournalLine::parse`, `journal::read_journal_and_format`,
|
||||
`Snapshot::read_new`, `Store::open`, `Store::declare_journal_format`,
|
||||
`StoreError::NewerJournal` (its `verified` flag picks the message).
|
||||
`Snapshot::read_new`, `Store::open`, `Store::create`, `store::db_file_for`,
|
||||
`store::database_in_use` (and `Snapshot::check_disk`, which checks the file
|
||||
it names), `Store::database_file`, `Store::other_databases`,
|
||||
`Store::declare_journal_format`, `Store::upgrade_format`,
|
||||
`Store::ensure_feature`, `Root::adopt`, `StoreError::NewerJournal` (its
|
||||
`verified` flag picks the message), `StoreError::FormatUpgradePending`,
|
||||
`ledger_store::backup::prove_archive`, ledger-mcp `Backups::upgrade_format`
|
||||
and `local.rs` (the passphrase), ledger-host `host.rs` (the lease intent,
|
||||
handed to the server) and `commands.rs` (`books upgrade`, asking at open),
|
||||
ledger-gui `owner.rs` (the notice and the dialog).
|
||||
**Checked:** `ledger-store` `tests/format.rs` (`old_reader_gets_newer_journal`,
|
||||
`a_damaged_line_that_looks_newer_is_corrupt_when_the_next_line_checks_it`,
|
||||
`an_unreadable_last_line_is_reported_as_newer_or_damaged`,
|
||||
@@ -1460,7 +1511,23 @@ owner's go-ahead before the first such declaration (plan D-39).
|
||||
`damaged_journals_are_still_corrupt`) and the unit tests in `format.rs`;
|
||||
`ledger-gui` `tests/newer_journal.rs`; `ledger-mcp` `tests/startup.rs`;
|
||||
the `fuzz_store_replay` target (the writer and the viewer agree on newer
|
||||
versus bad).
|
||||
versus bad). The upgrade: `ledger-store` `tests/format_upgrade.rs` (new
|
||||
books in this version's format, a change waiting for the upgrade, every
|
||||
old-books fixture upgrading to its figures, a newer feature still
|
||||
refused), the unit tests in `format.rs` and `backup.rs`
|
||||
(`the_archive_proves_to_hold_the_book_as_it_is_keys_and_all`),
|
||||
`tests/settings.rs` (`a_change_needs_journal_format_2`), `tests/root.rs`
|
||||
(adoption), `tests/schema_files.rs` (a P2-shaped books dir, its
|
||||
`wanderbooks.db` used at its schema and left byte for byte past it; an
|
||||
older schema's file left byte for byte and whole while this version's is
|
||||
built and kept up; a newer schema's file left alone, not refused; a damaged
|
||||
file of this version's schema rebuilt; new books' file named for their
|
||||
schema); `ledger-mcp` `local.rs` (`format_upgrade_waits_for_consent`,
|
||||
`no_upgrade_without_a_backup`,
|
||||
`no_upgrade_unless_the_backup_just_made_is_proven`); `ledger-host`
|
||||
`lease.rs` (`a_consent_never_shows_its_passphrase`); `ledger-cli`
|
||||
`tests/format_upgrade.rs`
|
||||
(from end to end); `ledger-gui` `tests/format_upgrade.rs`.
|
||||
|
||||
## B-28 What new commands take: four-digit years, amounts a line can carry
|
||||
|
||||
@@ -1508,7 +1575,11 @@ command's (`Ledger::add_line`, `register_asset`, `TaxBasis::check`,
|
||||
`replay_register_asset`, `TaxBasis::check_journaled`,
|
||||
`shares::check_shares_journaled`, ...), and every bound has its pair of
|
||||
tests: the command refuses past it; replay of an old line past it is
|
||||
accepted.
|
||||
accepted. What an earlier version left beside its journal is kept for it
|
||||
the same way: its projection database, in its own schema's file, is never
|
||||
migrated or written by a later version (B-27, "each projection schema in a
|
||||
file of its own"), so the earlier version, gone back to, opens its books
|
||||
as it left them.
|
||||
|
||||
**Derived figures refuse, never panic.** Because replay accepts what
|
||||
the commands now refuse, every figure the books derive is computed with
|
||||
@@ -1759,8 +1830,8 @@ by name, so a CPA who imported an earlier IIF is told.
|
||||
`check_rename_reason`,
|
||||
`replay_rename_account` (only that the account exists),
|
||||
`ChartOfAccounts::set_name`, `Ledger::add_control_account`;
|
||||
`Store::rename_account` (journal feature `account_renamed`, declared
|
||||
before the first rename, B-27); `books.rename_account` (scope
|
||||
`Store::rename_account` (journal feature `account_renamed`, which the
|
||||
books declare before their first rename, B-27); `books.rename_account` (scope
|
||||
`books:post`, like renumbering). **Checked:** `ledger-core`
|
||||
`tests/spec_ledger.rs` (`rename_account_*`), `tests/model.rs`
|
||||
(`Cmd::Rename`); `ledger-store` `tests/store.rs`
|
||||
@@ -1823,8 +1894,8 @@ included, and an account's header flag never changes).
|
||||
`check_tax_category_kept` (commands only, never `set_account_tax`, which
|
||||
replay calls); `settings::TaxCategoryPolicy`, `Settings::require_tax_category`;
|
||||
`Store::add_account_with_tax`, `add_account`, `set_account_tax`
|
||||
(journal feature `require_tax_category`, declared before the first
|
||||
`settings.set` naming the key); `ledger-mcp` `books.add_account`,
|
||||
(journal feature `require_tax_category`, which the books declare before
|
||||
their first `settings.set` naming the key, B-27); `ledger-mcp` `books.add_account`,
|
||||
`books.import_chart_csv`, `books.digest`; `ledger-gui` Needs attention
|
||||
(Info under warn, Todo under refuse). **Checked:** `ledger-core`
|
||||
`tests/tax.rs`; `ledger-store` `tests/store.rs`
|
||||
@@ -2214,7 +2285,7 @@ not the books' accounting method (`books.accounting_basis`).
|
||||
`shares::{target, split, resolve}`, `parties::{flow, money_accounts,
|
||||
card_accounts, MoneyAccounts::for_parties, activity_with, Activity::totals,
|
||||
Basis}`, `Ledger::set_party_creditor`, `Store::set_party_creditor` (the
|
||||
`party_creditors` journal feature, declared before its first line);
|
||||
`party_creditors` journal feature, asked for before its first line);
|
||||
`ledger-mcp` `books.get_entry` (`resolved`, `direction`, `cash`,
|
||||
`in_kind`), `books.party` and `books.parties` (`basis`), `books.update_party`
|
||||
(`creditor_of`); the viewer's party pages and entry page.
|
||||
|
||||
+113
-9
@@ -284,7 +284,7 @@ since `asset_long_life` alone takes three minutes a run):
|
||||
| Law | Test |
|
||||
|---|---|
|
||||
| Replay is pure: after any sequence of settings, convention, decision, open-item, template and approval commands, valid or refused, the live store, the store reopened from its journal and the viewer's snapshot hold the same settings and conventions, and the store verifies (database row for row) | `ledger-store` `tests/settings.rs` `settings_replay_is_pure` (proptest, 512 cases per check; 2,000 by hand, release, passed) |
|
||||
| The first such change declares journal format 2, once; a change to what is set journals nothing; refused changes journal nothing | `the_first_change_declares_format_2`, `refused_changes_journal_nothing` |
|
||||
| Settings need journal format 2: books this version made declared it as they were created, so a change journals only itself; a change to what is set journals nothing; books in format 1 refuse a change (nothing journaled) until their format upgrade, then make it after the one declaration; refused changes journal nothing | `a_change_needs_journal_format_2`, `refused_changes_journal_nothing` |
|
||||
| Approval rules block nothing until enforced; then an entry a rule covers posts only once its approver approved it as it stands | `approval_rules_off_by_default`; `ledger-mcp` `spec_kept.rs` `approval_rules_through_the_server` |
|
||||
| A template drafts exactly, odd cents by largest remainder, its slots named by each draft, all or nothing | `templates_draft_exactly_or_not_at_all`, `template_slots_take_the_account_each_draft_names`, `templates_draft_through_the_server`, core `splits_are_exact` |
|
||||
| Books written under P1 open to the same record forever | `books_written_under_p1_open_to_their_record` (fixture `old-books/p1_record`, also in every old-books test of the store, the viewer and the server) |
|
||||
@@ -1100,7 +1100,7 @@ no mutation run (D-33): the targets are listed for P12 below.
|
||||
`books_written_under_p3_keep_their_names` (fixture `old-books/p3_rename`,
|
||||
written once by `write_p3_rename_fixture`, `--ignored`; its figures are
|
||||
also in `books_an_earlier_version_wrote_open_to_its_figures`).
|
||||
- `ledger-store` `tests/format.rs`: `p3_features_are_declared_once_before_the_first_line_that_needs_them`,
|
||||
- `ledger-store` `tests/format.rs`: `p3_features_wait_for_the_upgrade_and_are_declared_once`,
|
||||
`an_unknown_feature_before_a_rename_is_newer_at_the_declaration`.
|
||||
|
||||
**Properties:** `prop_period_defaults_resolve` (`ledger-core`, 5,000
|
||||
@@ -1260,7 +1260,7 @@ no mutation run here; what P12 must cover is at the end.
|
||||
| Properties: a reversal's target, parts and flow are the original's negated; parts sum to the target, each with its sign; a sole party's money part is the flow as before P3; tin_last4 is every source present | `a_reversal_is_the_exact_opposite`, `a_sole_partys_money_part_is_the_flow_as_before`, `tin_last4_is_every_source_present` (`PROPTEST_CASES`, default 256; 5,000 run clean) | B-35, B-9 |
|
||||
| Decimal and i64 agree on party_target and reverse_target | `kernel_bridge.rs` `party_target_agrees` | the bridge |
|
||||
| Kani: the direction is the sign, the target is odd, reverse_target an involution | `proofs.rs` `party_target_direction_is_its_sign`, `party_target_is_odd`, `reverse_target_is_an_involution` | the kernel spec |
|
||||
| The journal: party.creditor_set after its feature, declared once; refusals journal nothing; books without creditors never change; corrupt lines refused | `ledger-store/tests/party_creditors.rs` | the format gate |
|
||||
| The journal: party.creditor_set one line each, under the feature the books declared when created; in books an older version wrote, refused (FormatUpgradePending, nothing journaled) until the format upgrade; refusals journal nothing; books without creditors never change, in either format; corrupt lines refused | `ledger-store/tests/party_creditors.rs` | the format gate |
|
||||
| Over the MCP: get_entry's direction, cash, in_kind; party totals by basis, echoed, others refused; basis all equals members' equity; creditor_of round trips and a card fee isn't a payment; the post_entry preview's parts are what it posts | `ledger-mcp/tests/spec_party_totals.rs` | the tool docs |
|
||||
| Defect 11 over the MCP: a member's TIN last four in the directory, a W-9's, none, after a purge | `ledger-mcp/tests/spec_vault.rs` `defect11_the_directory_shows_a_members_tin_last4` | B-26 |
|
||||
| The viewer shows the tools' numbers, labelled cash basis, with the in-kind line; its what-for reads a reversal as `parties::flow` does | `ledger-gui/tests/parties.rs` `the_party_page_shows_the_basis_and_in_kind`, `a_reversed_member_card_payment_is_nothing_on_the_issuers_page` | `activity_with`, `flow` |
|
||||
@@ -1312,8 +1312,8 @@ mutation run (D-33); the list P12 must cover is at the end.
|
||||
| The bucket table, all 16 cases; splitting and netting exact | `payment_bucket_is_the_table_in_all_16_cases`, `buckets_net_money_moved_between_the_companys_sources`, Kani `payment_bucket_is_the_table`, `payment_split_is_exact`, `net_buckets_keeps_the_total`, bridge `payment_buckets_agree` |
|
||||
| Properties | `no_instruments_no_financing_is_the_old_report` (the regression: with cash named and no methods or financing, the old figure exactly), `buckets_partition_each_payment`, `a_reversal_nets_every_bucket_to_zero`, `the_worksheet_ties_to_the_ledger` (paid + excluded + undetermined + not reportable + receipts for something else = every payment line in the year, dated decisions, sales and receipts included; the payment lists sum to their columns) |
|
||||
| Payment methods: refusals, the reversal chain | `a_payment_method_is_refused_unless_sound`, `a_payment_method_tags_the_whole_reversal_chain`, unit tests in `instruments.rs` |
|
||||
| W-9 flag kept apart from the decision (brief 1.5, R8) | `set_vendor_1099_keeps_the_w9_flag_when_omitted`, `set_w9_keeps_the_1099_decision_and_its_source`, `set_w9_declares_the_w9_flags_feature_before_its_first_line` (`spec_1099.rs`), `marking_a_w9_keeps_who_decided_the_1099` (`ledger-store` `tests/form1099.rs`) |
|
||||
| Journal: features declared once before their first line; replay and projection exact; refusals and no-ops journal nothing; newer values newer; old books as they always meant | `ledger-store` `tests/form1099.rs` (`each_feature_is_declared_once_before_its_first_line`, `decisions_and_methods_replay_and_project`, `refusals_and_no_ops_journal_nothing`, `an_unknown_1099_value_is_newer_not_corrupt`, `books_written_before_decisions_read_as_they_always_meant`), unit tests in `ledger-store` `form1099.rs`, the op model (`refused_commands_never_reach_the_journal` with `SetVendor1099`, `SetW9Flag`, `AddInstrument`, `SetEntryInstrument`; an accepted command journals or changes nothing), `books_an_earlier_version_wrote_open_to_its_figures` (fixture `p2_vendor_1099`) |
|
||||
| W-9 flag kept apart from the decision (brief 1.5, R8) | `set_vendor_1099_keeps_the_w9_flag_when_omitted`, `set_w9_keeps_the_1099_decision_and_its_source`, `set_w9_asks_for_the_w9_flags_feature_before_its_first_line` (`spec_1099.rs`: refused whole in P2's books until their upgrade), `marking_a_w9_keeps_who_decided_the_1099` (`ledger-store` `tests/form1099.rs`) |
|
||||
| Journal: lines written only under their features (refused, nothing journaled, in books an older version wrote until the format upgrade; never declared on the way); replay and projection exact; refusals and no-ops journal nothing; newer values newer; old books as they always meant | `ledger-store` `tests/form1099.rs` (`each_feature_waits_for_the_upgrade_and_is_never_declared_on_the_way`, `decisions_and_methods_replay_and_project`, `refusals_and_no_ops_journal_nothing`, `an_unknown_1099_value_is_newer_not_corrupt`, `books_written_before_decisions_read_as_they_always_meant`), unit tests in `ledger-store` `form1099.rs`, the op model (`refused_commands_never_reach_the_journal` with `SetVendor1099`, `SetW9Flag`, `AddInstrument`, `SetEntryInstrument`; an accepted command journals or changes nothing), `books_an_earlier_version_wrote_open_to_its_figures` (fixture `p2_vendor_1099`) |
|
||||
| Records change no entry (verification 13a) | `decision_and_method_records_change_no_entry` |
|
||||
| The quieter untagged note (R7) | `the_untagged_note_counts_only_money_out` (`ledger-package`) |
|
||||
| One builder: the package CSV is the worksheet CSV for the package's accounts; README wording | `ledger-package` `tests/package.rs` (the zip's CSV equals `vendor_1099_csv` for the request's accounts, else the settings', through as_of: `payments_to_a_vendor_come_from_the_packages_accounts`, `one_list_of_cash_accounts_per_package`, `the_1099_worksheet_stops_at_as_of`; the README line pinned); over the MCP, the package's CSV is `books.vendor_1099`'s for the same `cash_accounts`, `financing_accounts` and `through` (`ledger-mcp` `oracle_mcp.rs` `cpa_package_holds_the_year_and_its_evidence`) |
|
||||
@@ -1357,7 +1357,7 @@ must cover is at the end.
|
||||
| Party totals and the 1099 worksheet never contradict: paid less received is the worksheet's paid + excluded_card + undetermined + not_reportable plus its other receipts, for a vendor named alone, whatever the methods, refunds and reversals | property `party_totals_are_the_1099s_buckets` (`ledger-core` `tests/form1099.rs`; `PROPTEST_CASES`, default 256; 2,000 run clean; dropping a bucket from the sum fails at once) | B-35 item 9, by hand |
|
||||
| The same, one year by hand: a member's check, a member's card, a member's payment of unknown method, a company card charge and a company check to one contractor, then the member reimbursed | `bookspec/examples/1099-and-party-totals-for-a-members-payment.toml` | B-35 item 9; Treas. Reg. §1.6041-1(e)(1), (a)(1)(iv) |
|
||||
| The CPA package's 1099 worksheet is `books.vendor_1099`'s CSV for the package's accounts (the request's, else the settings', R18) through its as_of, byte for byte; without `through` the files differ; a `through` outside the year and an unknown account are refused | `ledger-mcp` `oracle_mcp.rs` `cpa_package_holds_the_year_and_its_evidence`; `ledger-package` `tests/package.rs` `payments_to_a_vendor_come_from_the_packages_accounts`, `one_list_of_cash_accounts_per_package`, `the_1099_worksheet_stops_at_as_of` | B-33; mcp-interface.md |
|
||||
| A feature is declared before a command's first line: `books.update_party` with a field and the first creditor journals the declaration, then the field, then the creditor | `ledger-mcp` `tests/spec_party_totals.rs` `update_party_declares_the_creditor_feature_before_its_first_line` | B-27 |
|
||||
| A feature is asked for before a command's first line: `books.update_party` with a field and the first creditor, in P2's books waiting for their format upgrade, is refused whole (nothing journaled, never the field without the creditor); once upgraded, as in books this version made, it journals the field, then the creditor, declaring nothing | `ledger-mcp` `tests/spec_party_totals.rs` `update_party_asks_for_the_creditor_feature_before_its_first_line` | B-27 |
|
||||
| One bookspec loader for every example, the parties' and the 1099s' with them: `setup.parties`, `setup.members` (`distributions_account`), `setup.payees` (a party's vendor record), `setup.status_changes`, `setup.instruments`, events' `parties` and `instrument`, and the queries `vendor_1099`, `k1`, `party_part`, `party_totals`, `party_tin_last4`; refused: a party or payment method not in the setup, a reversal naming parties, party totals without a basis, a 1099 line leaving a figure out, a wrong direction or status | `ledger-core` `tests/bookspec.rs` (`every_bookspec_example_holds`, `a_party_not_in_the_setup_is_refused`, `a_party_parts_direction_is_checked`, `a_reversal_naming_parties_is_refused`, `party_totals_without_a_basis_are_refused`, `a_vendor_1099_line_leaving_a_figure_out_is_refused`, `a_vendor_1099_status_is_checked`, `a_payment_method_not_in_the_setup_is_refused`) | bookspec/README.md |
|
||||
|
||||
**For P12 (mutation targets, none run, D-33):** `parties::card_accounts`
|
||||
@@ -1385,7 +1385,7 @@ a bookspec example (`bookspec/examples/`, P13, D-34).
|
||||
| A disposal inside a month already run takes back the days after it (R2: excess 500.00, book value 6,500.00) | `defect22_a_disposal_inside_a_posted_month_takes_back_the_extra_days` (store and `ledger-mcp` `spec_assets.rs`) |
|
||||
| A month skipped is caught up in the month the run is made; a later run for it posts nothing (settled in design, P3) | core `a_skipped_month_is_caught_up_in_the_month_run`, store `a_skipped_month_is_caught_up_in_the_month_run`, MCP `a_skipped_month_is_caught_up_and_said` |
|
||||
| A reversed run is un-posted and runs again (feature month_reposting); re-instating a run whose month ran again is refused; a disposal, and a run its disposal took as final, can't be reversed; a reversed run before a disposal leaves nothing behind (R3) | store `a_reversed_depreciation_month_can_be_run_again`, `a_disposal_entry_cannot_be_reversed`, `a_reversed_run_before_a_disposal_leaves_nothing_behind`; core `a_reversed_schedule_month_is_due_again` |
|
||||
| What the books carry is read entry by entry, each in its own month: a run reversed after its month ended isn't run twice (its month posts nothing; the reversal's month catches it up), for depreciation; a schedule month reversed after it ended is left for a human, listed with why (P3 review) | store `a_run_reversed_after_its_month_is_caught_up_in_the_reversals_month`; GUI `a_run_reversed_after_its_month_leaves_the_reversals_month_due`; core `schedules.rs` `a_schedule_month_reversed_after_it_ended_is_left_for_a_human`; store `format.rs` `a_schedule_month_posted_again_declares_month_reposting_and_replays` |
|
||||
| What the books carry is read entry by entry, each in its own month: a run reversed after its month ended isn't run twice (its month posts nothing; the reversal's month catches it up), for depreciation; a schedule month reversed after it ended is left for a human, listed with why (P3 review) | store `a_run_reversed_after_its_month_is_caught_up_in_the_reversals_month`; GUI `a_run_reversed_after_its_month_leaves_the_reversals_month_due`; core `schedules.rs` `a_schedule_month_reversed_after_it_ended_is_left_for_a_human`; store `format.rs` `a_schedule_month_posted_again_waits_for_month_reposting_and_replays` |
|
||||
| Re-instating a run (reversing its reversal) never carries the books past the schedule, nor past cost less salvage (P3 review) | store `reinstating_a_run_a_later_run_caught_up_is_refused` |
|
||||
| What the books carry is each run and each reversal in its own month, alternately adding and taking away (P3 review) | core `spec_assets_payroll.rs` `depreciation_effects_read_each_entry_in_its_month` |
|
||||
| A schedule month that rounds to nothing is never a posting: left out of a daily plan, never due in an equal-months one (P3 review; no test held it before) | core `schedules.rs` `a_month_that_rounds_to_nothing_is_never_a_posting` |
|
||||
@@ -1399,7 +1399,7 @@ a bookspec example (`bookspec/examples/`, P13, D-34).
|
||||
| An equal-months schedule starts on the 1st (D-37); a daily one's last day is in the calendar | `an_equal_months_schedule_starts_on_the_first`, `spec_bounds.rs` `a_daily_schedule_whose_last_day_passes_9999_is_refused` |
|
||||
| chrono's month-end clamping is pinned for daily plans (Jan 29, 30, 31; Feb 29) | `schedules.rs` unit `daily_plans_pin_month_end_clamping` |
|
||||
| A new asset's cost and salvage are in the minor unit (replay takes finer ones, computed); a life ending 9999-12-31 is taken, a day later refused | `spec_bounds.rs` `an_asset_cost_finer_than_a_cent_is_refused`, `a_life_ending_on_9999_12_31_is_accepted`, `a_life_ending_after_it_is_refused` |
|
||||
| The first daily schedule declares feature schedule_convention; an older reader stops there; monthly_even declares nothing; an unknown convention is newer, never corrupt | `ledger-store` `tests/format.rs` `the_first_daily_schedule_declares_its_feature_and_an_older_reader_stops_there`, `a_monthly_even_schedule_declares_no_feature`, `an_unknown_schedule_convention_is_newer_not_corrupt`; `format.rs` unit `an_older_reader_stops_at_p3s_features` |
|
||||
| A daily schedule needs feature schedule_convention (books an older version wrote wait for the upgrade); an older reader stops at its declaration; monthly_even needs none; an unknown convention is newer, never corrupt | `ledger-store` `tests/format.rs` `a_daily_schedule_waits_for_its_feature_and_an_older_reader_stops_there`, `a_monthly_even_schedule_declares_no_feature`, `an_unknown_schedule_convention_is_newer_not_corrupt`; `format.rs` unit `an_older_reader_stops_at_p3s_features` |
|
||||
| Accumulated depreciation never falls, never exceeds cost − salvage, is 0 before service and the whole from the last day (sampled days: the first and final months, two between, a dozen month ends; R9) | property `prop_depreciation_is_monotone_and_bounded` (500 cases) |
|
||||
| No posted month moves but a half-cent tie's cent (old Decimal arithmetic as the oracle) | property `prop_month_end_schedules_match_the_legacy_arithmetic_except_half_cent_ties` (500 cases) |
|
||||
| A daily plan sums exactly, lands on month ends, each amount > 0; from the 1st, `months` postings within a cent of total/months | property `prop_daily_plan_sums_exactly_and_lands_on_month_ends` (2,000 cases) |
|
||||
@@ -1410,7 +1410,7 @@ a bookspec example (`bookspec/examples/`, P13, D-34).
|
||||
| A depreciation run's or schedule month's reversal can't be dated before the entry it reverses (P3 review round 2: a run of March reversed on Jan 31, then a disposal on Feb 15, ended February with a 1,000.00 debit in accumulated depreciation) | store `a_reversal_dated_before_the_entry_it_reverses_is_refused` |
|
||||
| A disposal waits only until what the runs carry at its month's end is what they carry now, so a late change undone after its month closed lets it through (P3 review round 2; round 1 refused it for ever) | store `a_disposal_is_taken_once_a_late_change_is_undone_after_its_month_closed` |
|
||||
| Depreciation still due when the disposal's month has a run is charged in the disposal entry, never as a second run of the month (P3 review round 2; no store test held it) | store `a_disposal_whose_month_has_run_charges_what_is_still_due_in_its_entry` |
|
||||
| A command that needs a journal feature decides and declares it before its first journaled line: a refused feature journals nothing (the format-upgrade gate) | store `format.rs` `a_refused_journal_feature_journals_nothing` |
|
||||
| A command that needs a journal feature asks for it before its first journaled line: in books waiting for the format upgrade it is refused and journals nothing, and after the upgrade it is taken | store `format.rs` `a_write_waiting_for_the_upgrade_journals_nothing` |
|
||||
| What a viewer's one pass says a run of each month posts is run_amount's for that month | property `prop_run_amounts_are_each_months_run_amount` (500 cases); Kani `catch_ups_is_every_suffixs_catch_up` |
|
||||
| The kernel on i64 and i32 (Kani's) is the kernel on i128 and Decimal (the engine's) | `kernel_bridge.rs` `prorate_agrees_at_i64_and_i128`, `prorated_through_agrees_at_i32_and_i128`, `catch_up_and_true_up_agree` |
|
||||
| The charge never falls and is continuous at month ends, over every 1-to-3-month span from every day of a common and a leap year | `kernel.rs` unit `the_charge_never_falls_and_is_continuous_over_real_calendars` (exhaustive) |
|
||||
@@ -1450,6 +1450,110 @@ projection upsert, `period_month`; `format.rs` `KNOWN_FEATURES`,
|
||||
and `schedule`'s disposed months. Equivalent mutants foreseen:
|
||||
none (`r >= step - r` against `>` is killed by `half_a_unit_rounds_up`).
|
||||
|
||||
## P3: the format upgrade (2026-10-20)
|
||||
|
||||
D-39 and plan/served.md 3.9 (`format_upgrade_pending`): books an older
|
||||
wanderbooks wrote wait for the owner's go-ahead before they move to this
|
||||
version's journal format (rules.md B-27). Mutation testing waits for P12
|
||||
(D-33); the targets are listed at the end.
|
||||
|
||||
- `ledger-store` `format.rs` unit tests: this version's format is the
|
||||
newest with every known feature; books wait exactly when behind (a
|
||||
synthetic version with features covers what main can't yet: features
|
||||
missing one at a time, all added at once, a format ahead never
|
||||
"behind"); a write needs what the books declared, and the refusal
|
||||
names the guard, says nothing changed, and that only the owner can
|
||||
agree; a reader that doesn't know a declared feature still refuses it
|
||||
as newer.
|
||||
- `ledger-store` `tests/format_upgrade.rs`: new books declare the format
|
||||
as they are created; older books open without a byte changing, a change
|
||||
needing format 2 (settings, a convention, an open item) is refused
|
||||
journaling nothing while a change needing nothing new is made, the
|
||||
upgrade is one line with the whole format, again nothing, the figures
|
||||
unchanged; every old-books fixture (30) upgrades, verifies as it did and
|
||||
replays to its expected.json in the writer, reopened, and the viewer; a
|
||||
declared newer feature is still refused as newer. `tests/settings.rs`
|
||||
(`a_change_needs_journal_format_2`, the lazy declaration it replaces),
|
||||
`tests/root.rs` (an adoption's copy moves to this version's format; the
|
||||
source keeps its own), `tests/format.rs` (its books are format 1, as
|
||||
before the gate).
|
||||
- `ledger-store` `tests/schema_files.rs` (each projection schema in a file
|
||||
of its own, the lead's decision of 2026-10-21): a P2-shaped books dir
|
||||
(`tests/fixtures/p2-books`, its `wanderbooks.db` at schema 12, written by
|
||||
`write_the_p2_fixture` while this code's schema was P2's) used as it is
|
||||
at its schema and left byte for byte, and whole, past it; a legacy file
|
||||
one schema older than this version's, on every version, left byte for
|
||||
byte and whole for its schema while this version's is built from the
|
||||
journal and kept up, the report and the viewer naming the one in use; a
|
||||
newer schema's file, under its own name or the legacy one, left alone,
|
||||
never refused; this version's own damaged (not a database, or holding
|
||||
another schema) kept aside and rebuilt, a damaged legacy one left; new
|
||||
books' file named for its schema. Checked by breaking it (the legacy
|
||||
file always taken, the schema check dropped): three tests fail. The
|
||||
store's other tests use `db_file()`, this version's file.
|
||||
- `ledger-store` `backup.rs`: `the_archive_proves_to_hold_the_book_as_it_is_keys_and_all`
|
||||
(a changed or extra key, a changed vault record, books that moved on, a
|
||||
journal changed under the recorded head: each refused; the scratch
|
||||
folder always removed).
|
||||
- `ledger-mcp` `local.rs`: `format_upgrade_waits_for_consent` (listed as
|
||||
waiting; no passphrase set, a wrong one or none refused before anything
|
||||
is done, no backup, no line, still served; with it, the backup is the
|
||||
archive's latest commit, holding the journal as it was and the key; one
|
||||
line, the whole format; on the backup record; again, nothing; new books
|
||||
wait for nothing, and asking for their upgrade makes not even a backup
|
||||
repository), `no_upgrade_without_a_backup` (a backup refused: nothing
|
||||
upgraded, still waiting, recorded) and
|
||||
`no_upgrade_unless_the_backup_just_made_is_proven` (an archive whose
|
||||
head names another branch takes the commit where no clone finds it:
|
||||
what is proven isn't what was made, nothing upgraded, both recorded;
|
||||
put right, the latest commit, which already holds the books, is proven
|
||||
and the upgrade made). Each checked by breaking the code it guards (the
|
||||
proof skipped, the made-is-proven check dropped, the early return for
|
||||
books that wait for nothing removed): the test fails, then passes once
|
||||
restored.
|
||||
- `ledger-cli` `tests/format_upgrade.rs` (the plan's
|
||||
`format_upgrade_waits_for_consent`, from end to end through the one
|
||||
executable): a command not at a terminal says the books wait; `books
|
||||
list` and `doctor` say so; `books.start` names it and puts the handbook's
|
||||
format-upgrade section first; `books.set_setting` refused with the
|
||||
guard, journaling nothing, while adding an account works; no tool's name
|
||||
could consent, and no call moved the format; `books upgrade` refused off
|
||||
a terminal; the lease intent with a wrong passphrase refused; at a
|
||||
terminal an empty passphrase declines, and `books list` asks at each of
|
||||
two opens, while one whose output goes to a file only notes it on the
|
||||
terminal and asks nothing, its output its own; with the passphrase, the
|
||||
backup (journal as it was, the key)
|
||||
then one line, the agent's same session then sets the setting and
|
||||
verifies; asked again, nothing.
|
||||
- `ledger-gui` `tests/format_upgrade.rs`: the dialog at open and at the
|
||||
next open, "Not now" leaving the standing notice (whose button, and the
|
||||
Books menu, ask again), the viewer writing nothing; nothing sent without
|
||||
a passphrase, the one typed handed to the owner as typed, a refusal said
|
||||
in the dialog, which stays, the right one closing it; books in this
|
||||
version's format and older books dirs never asked; an upgrade whose
|
||||
worker dies without an answer is said in the dialog, which can then be
|
||||
closed (never left at work, holding the window). Screenshots looked
|
||||
at (`SHOT_DIR=... cargo test -p ledger-gui --test format_upgrade
|
||||
screenshots`): the dialog over the overview, at work, refused, the
|
||||
notice, the menu item, done.
|
||||
- Unit tests in `admin.rs` (the passphrase never in Debug; the request's
|
||||
shape), `ledger-host` `lease.rs` (`a_consent_never_shows_its_passphrase`:
|
||||
the lease intent's Debug, and the line's, name no passphrase), and
|
||||
`owner_menu.rs`'s fake owner takes the new call.
|
||||
|
||||
**For P12:** `ledger-store` `format.rs` (`current`, `upgrade_to`, `adds`,
|
||||
`require_format`, `require_feature`), `store.rs` (`create_in_format`,
|
||||
`ensure_format`, `ensure_feature`, `upgrade_format`), `root.rs`
|
||||
(adoption's upgrade and its verify), `backup.rs` (`prove_archive`,
|
||||
`held_files`); `ledger-mcp` `backup.rs` (`upgrade_format`,
|
||||
`UpgradeFailed::parts`), `local.rs` (`upgrade_format`, the List's
|
||||
`format_upgrade`, `waiting`), `server.rs` (`with_store_mut`),
|
||||
`tools/kept.rs` (the first next step, `format_upgrade`), `admin.rs`;
|
||||
`ledger-host` `host.rs` (`consent`, `consented`), `commands.rs`
|
||||
(`consent_now`, `upgrade_format`, `offer_upgrades`, `offer_upgrade`,
|
||||
`upgrade`, `book_line`, `upgraded_lines`, `upgrade_explained`,
|
||||
`ASKED_UPGRADES`). None run.
|
||||
|
||||
## Benchmarks (baseline 2026-10-13)
|
||||
|
||||
No budget, by decision (the owner, 2026-10-13: the books do little work
|
||||
|
||||
Reference in new issue
Block a user