fix: Windows reads of lock files, update paths cfg(unix), merge main again (P11c)
check / check (push) Canceled after 0s
check / portable (macos-latest) (push) Canceled after 0s
check / portable (windows-latest) (push) Canceled after 0s
two-user / two-user (push) Canceled after 0s

spec_vault swept every file under a book, lock files included; on Windows
a held LockFileEx lock is mandatory and the read failed. The locks are
already dedicated files (never data), so the test skips *.lock. Merged
main (P13 step 6, the macOS server.log assert, P11a's AppImage identity):
host.rs keeps main's five-field identity on Unix and size/last-write on
Windows; the update poll and set_program_version are Unix-only.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HS8WZziDnyxunC23MUxrLp
This commit is contained in:
swaitsandClaude Fable 5.1 committed 2026-10-29 17:03:30 -06:00
commit 7749afc557
234 files changed
+13028 -18411

No files matched your search

+36 -13
View File
@@ -678,7 +678,7 @@ The `WANDERBOOKS_TOML` template (`ledger-cli/src/main.rs:237-347`) becomes the e
| P4 | `list_drafts`. Leftover drafts are discarded or set to `awaiting_evidence`. | landed 2026-10-29 (`b97234ac`; schema 19; `Store::post_entry` itself is not gated, the server's `post_entry_checked` is: see the As-built block)
| P6 | • `consolidate_duplicates` resolves the 11 redundant records (preview first).<br>• Extraction is backfilled for all 66 attachments.<br>• Receipt numbers 1000–1029 are annotated from the "Receipt ID" in each description. Anomalies become annotations: 1009 unissued; 1011/1020 byte-identical pairs (merged); 43/45/47 have no ID. The next number is derived as 1030.<br>• Instruments: Quillmere, joint Amex, member cards, Chase Ink 2050.<br>~~• `verify-sheet.py`'s tables become a `mapping` convention, then `compare_reference` runs against attachment 37 as of 2026-10-11.<br>• `drive-manifest.json` text goes to `record_inventory`; the Drive connector listing goes to `check_inventory`.~~ → not built (8.4): the final `verify-sheet` run is a filed workpaper (P2 pre-cutover) and P10 cites it. | Every attachment's `get_receipt` default answer under 8 KiB; the 11 duplicates consolidated; receipt numbers annotated |
| ~~P7~~ | ~~`wanderbooks vault init` … `keys/legacy.key` shredded~~ → D-48: nothing; the v1 key stays in the books and in every backup (D-31) | — |
| P10 | Re-audit (8.2 P10; `docs/runbooks/re-audit.md`). | the exit criteria there |
| P10 | Re-audit (8.2 P10; `docs/runbooks/re-audit.md`). | the engineering deliverable landed 2026-10-29 (`docs/runbooks/re-audit.md`, the defect regression table); the re-audit itself is the owner's run
---
@@ -826,19 +826,19 @@ One row per unit of work. A unit is what one engineer takes into one git worktre
| P4b S6 | Paying on terms: multi-document payments, discounts, the processor fee, bills due, aging exports | landed 2026-10-29 (`be580598`; no schema change; law 36); `confirmed` deferred to P4 | S4.1 (`Terms`), U0a | S5, S7, P6 | `documents_v2` | 8-10 |
| P4b S7 | Bad debts (write-offs) | landed 2026-10-29 (`7ebeeed2`; law 37; `just prove` green) | S1; S4.3 for the same-call evidence attach; U0a | S4.4 onward, S5, S6 | `documents_v2` | 4-6 |
| P4 | Draft states, the evidence gate in one place, the preview digest, the digest tool | not started | S4-S7 merged (one sweep over every confirm tool), U0b | P6, P9a, P11a, P13 | `drafts_v2` (same upgrade as `documents_v2`, D-52) | 8-10 |
| P5 | Agent surface complete: the result envelope, prompts, `books.start` v2, journal history, agent evals | in progress (wave 2, branch `P5`); merges after P8 | P4, U0b; the envelope step is single-owner and merges when no other MCP branch is open | P6, P9a, P11a, P13 | `source_revisions` | 10-12 |
| P5 | Agent surface complete: the result envelope, prompts, `books.start` v2, journal history, agent evals | landed 2026-10-29 (`d9d63b16`; no schema change; the viewer keeps its own describer, see the As-built block) | P4, U0b; the envelope step is single-owner and merges when no other MCP branch is open | P6, P9a, P11a, P13 | `source_revisions` | 10-12 |
| P6 | Evidence pipeline: extraction, dedup, annotations, company record categories | landed 2026-10-29 (`9351f89d`, schema 18, with the integration fixes `b66fc893`, `0cbb49b6`, `fb83af59`, `6c26160e`, `bfd453b6`); the `ledger-store` → `ledger-import` dependency is Q-9 | U0a, U0b | S4-S7, P4, P5 (not its envelope step), P9a, P11a, P13 | `attachment_text` | 8-10 |
| P7 | The TIN scanner's separators; vault v2 closed (D-48) | landed 2026-10-26 (`3e65c83f`) | none | everything | none | 1 |
| P8 | Audit and attestations | in progress (wave 2, branch `P8`) | P6 (extraction), P4 (drafts), U0b | P9a, P11a, P13 | `audits` | 10-12 |
| P8 | Audit and attestations | landed 2026-10-29 (schema 20; `policy.cpa_package_requires_audit` unset refuses once the books hold an audit run, see the As-built block) | P6 (extraction), P4 (drafts), U0b | P9a, P11a, P13 | `audits` | 10-12 |
| P9a | Self-host recipe, `books adopt` move with the head check, static bearer over a tunnel, the two-user CI job | landed 2026-10-27 (`c8541ea9`); Done-when 1 verified 2026-10-29 (the two-user job green on `fb83af59`, after `79fb7ff2` gave the server account the fixture copies); Done-when 2 (the recipe on a clean VM) is the owner's | none (code); a second machine to try it on | everything | none | 3-4 |
| P9b | claude.ai through an external identity provider (D-7 b, Q-6) | not planned until Q-6 is answered | P9a | | | |
| P9c | Remote viewer replica, invitations | not planned (D-51) | | | | |
| P10 | The re-audit runbook and its CI mirror; the owner runs the re-audit | not started | P5, P6, P8 | P11a, P13 | none | 1 (+ owner days) |
| P11a | Packaging and updates: step 0 the platform groundwork, the trial, `wanderbooks update`/`rollback`, Linux packages, signing, macOS and Windows bundles | in progress: step 0 landed 2026-10-29 (`5e220cca`) for the macOS keyring store and the portable CI job; **the keyring stores go away under D-63 (unit U0e)** and native Windows is its own unit P11c (D-62); steps 1-7 open, macOS and Linux first | none (code); certificates and keys from the owner for the signing steps; Q-4 before the manifest step | everything | none | 20-25 |
| P11a | Packaging and updates: step 0 the platform groundwork, the trial, `wanderbooks update`/`rollback`, Linux packages, signing, macOS and Windows bundles | in progress: steps 0-4 and the macOS half of step 6 landed 2026-10-29 (update/rollback, Linux packages, AppImage with restart, the daily staged update, the unsigned `.dmg` job); step 5 (signing) deferred by D-64; left: the in-app accept and the viewer's update notice (names needed), the release workflow's first run (owner's click), the trials on a Mac | none (code); certificates and keys from the owner for the signing steps; Q-4 before the manifest step | everything | none | 20-25 |
| P11b | The agent dock and books homes | not started | P11a (an installed app to dock into) | P12 | none | 8-10 |
| P11c | The native Windows port: named pipes with the SID check, owner-only DACLs, file locking, the `%LOCALAPPDATA%` data root, the Windows CI job made gating (D-62) | in progress (wave 2, branch `P11c`) | U0e; P11a step 0 (the portable job) | P11b, P12 | none | 5-8 |
| P12 | Max testing, again | not started | everything else merged | | none | 2-3 (+ machine time) |
| P13 | bookspec: checker crate, `Engine` trait, rule ids, the spec text, the runner | in progress: steps 1-3 landed 2026-10-26 (`579d0eed`); steps 4-5 landed 2026-10-29 (`d839e709`; ten stated rules have no example yet because the schema has no event or query for them, listed in the As-built block); step 6 (the spec's own public repository) is the owner's call | none for steps 1-4; the engine stable for step 5 | everything | none | 15-20 |
| P13 | bookspec: checker crate, `Engine` trait, rule ids, the spec text, the runner | in progress: steps 1-5 landed (`579d0eed`, `d839e709`); step 6 (the spec's own public repository) decided 2026-10-29 (D-65): split out now | none for steps 1-4; the engine stable for step 5 | everything | none | 15-20 |
#### 8.0.2 Dependency graph and merge order
@@ -1515,7 +1515,15 @@ Each unit follows the 8.0.4 template. "A-n" is an owner answer numbered in 8.3.1
5. **Files:** owns `crates/ledger-mcp/src/guards.rs`, `tools/prompts.rs`, `prompts/*.md`, `crates/ledger-store/src/describe.rs`, `crates/ledger-cli/tests/agent_evals/`; touches `server.rs` (the result path), `tools/kept.rs`, `opening.rs`, `bridge.rs`, `handbook.rs`, the 8.0.3 files, `docs/mcp-interface.md`.
6. **Tests and proofs:** named above; no kernel or Lean change.
7. **Done when:** (1) every mutating result carries `head` and non-empty `next_steps`; (2) every guard's remedy tool exists; (3) `books.start` ≤ 8 KiB on the `p2-served` fixture and on the demo books; (4) the prompts list and render; (5) `tool_descriptions_fit_700_characters` pins 0; (6) the six evals pass; (7) `just check` green. The live check (a fresh Claude Code session in an empty folder, and a Codex session, each posting an entry by following `next_steps`) is the EM's, recorded in the As-built block.
8. **As built:** none yet.
8. **As built (2026-10-29, branch P5, eight commits on b97234ac; no schema change, so no migration number was taken):**
- Step 1 as written, with these shapes: `next_steps` items are `{ tool | prompt | command, args?, why, required }` (the `command` form carries `books.export_cpa_package`'s download command, which is not a tool); a result that already has `next_steps` or `head` keeps it (`books.start` has its own `next_steps` and `head`, `books.verify` and `books.access_log` their own `head`); the envelope adds no `warnings` of its own (a tool's own stay as they are, `books.cash_flow`'s object warnings included) and `guards` is on a refusal only; `head` and `next_steps` are on every tool that is not read-only (its annotation), `head` being the journal's head after the call; a read has `next_steps` only when something follows it, because a resource and its tool must say the same (`the_kept_resources_read` compares `books://settings` with `books.settings`), and an empty `warnings` on `books.add_account` broke `add_account_warns_without_tax_category_defect_8`. A refusal that is a guard carries `structuredContent.guards`; its text is unchanged. After a close the step is `books.verify`; **P8 replaces it with `attest_period` when it merges**. The guard registry has ten codes (the plan's seven examples plus `party_shares`, `party_required`, `open_drafts`); `untagged_control_line`, `duplicate_vendor_ref` and `first_number_locked` are in the registry and recognized from a refusal that begins with the code, but no tool refuses with them yet. The Needs-attention items are added after a change to an entry or an invoice only (not to a party or an account). `every_guard_names_an_existing_tool` is in `guards.rs`, `every_mutating_result_has_head_and_next_steps` in `server_tests.rs`.
- Step 2 as written, except: `head.last_handoff`, `rules.attestation_bar`, `rules.approval_rules` as a list and `attestation` are not in v2 (P8's); `you.verified`/`self_reported` and `isolation`'s shape were not added to `you` (the plan's `you` has four keys and has them). `agent` is stored on the access record as `agent {model?, reasoning_effort?, harness?}` (a new optional field of `ledger_store::access::Access`; the access log is its own chain, not the journal, so 8.0.6 does not apply). `you` is absent on the `books://start` resource (no token made that call). The fit loop shortens attention items to four and waiting drafts to three before the lists.
- Step 3 as written. The prompt tests are in `crates/ledger-cli/tests/agent_surface.rs` (`the_prompts_list_and_render`), not `crates/ledger-mcp/tests/prompts.rs`: the prompt renderer is `pub(crate)` and the p2-served harness is in ledger-cli; the "golden render" is each prompt's file with its blanks filled, compared exactly, rather than a stored copy. Prompts need `books:read`. The report resources now take their dates from the query; `books://entries/{id}` and `books://journal/{seq}` are new reads; `books://attachments/{id}` was already served.
- Step 4: `books.journal_history` as written, in `tools/journal.rs`, `subject` being the line's first subject. **Departure:** the describer in `crates/ledger-store/src/describe.rs (new)` is the server's; the viewer's `describe` arms in `ledger-gui/src/pages/journal.rs` were not moved (they carry icons, routes and today's party and account names; moving them behind a formatting trait was a large change to the viewer for no figure or behavior). So there are two describers; the server's quotes only identifiers, dates, amounts and short typed names and never a setting's value. Decide with the EM whether to unify them (U-unit).
- Step 5: parameter names are `reason` (on `books.set_convention`) and `revises` with `reason` (on `books.record_decision` and `books.raise_item`); `subject`, `decision_md` and `title` became optional in those two schemas so a source-only call can leave them out (given, they must equal the record's). Test names: `a_source_only_revision_is_journaled`, `source_revisions_wait_for_the_upgrade` (both in `crates/ledger-store/tests/source_revisions.rs`), plus `a_source_revision_must_change_only_the_source` and the over-the-wire `a_source_only_revision_over_the_wire`. 8.0.6: all six items (the `p5_before` fixture was written by b97234ac; no golden moved).
- Step 6: the eight descriptions were moved into the handbook sections `cpa-handoff`, `parties-and-tins`, `1099`, `files-in-out`, `reconciliation` and `viewer`; `tool_descriptions_fit_700_characters` pins none and the allowance list is gone. `instructions_budget` was already green with the final connect text (it already had no `preview_token`, reversal drafts or `request_tin_entry`). The aliases are removed (and `customer`/`vendor` are required); every over-the-wire test that used them was changed to the new names (`party_id_alias_still_works` became `a_customer_by_id_and_by_name_is_one_record`). `docs/mcp-interface.md`'s overview sections now describe the surface as built.
- Step 7: the six transcripts are as named. Each file may have `setup` (direct calls that make the chart and the books an agent finds; not the agent's moves), `settings`, and steps `call` (only `books.start`, first), `follow`, `follow_prompt`, `expect_next_steps_contains` and `expect`; `{"$upload": ..}` and `{"$var": ..}` stand for an uploaded file's id and a value a setup call saved. The runner is `agent_evals.rs`.
- Not run by the worker (D-60): `just check`, `just prove`; the live check of Done-when (7) (a fresh Claude Code session in an empty folder and a Codex session, each posting an entry by following `next_steps`) is the EM's. Checked: `cargo test -p ledger-mcp`, the store tests `source_revisions`, `format` and `store`, and the over-the-wire files this unit edited, with fmt and clippy on the five crates touched.
**Struck from P5:** ~~`books.job` and the 20 s rule~~ → D-53 (no measured call approaches a client limit over the bridge; jobs come with P9b if claude.ai's 240 s limit is ever real); ~~URL-mode elicitation~~ → nothing local to elicit (D-28); ~~`ledger-view` crate~~ → U0b (`PackageBooks`); ~~`books.viewer_status`~~ → `books.show` already reports `viewer_open`.
@@ -1581,7 +1589,19 @@ Each unit follows the 8.0.4 template. "A-n" is an owner answer numbered in 8.3.1
5. **Files:** owns `crates/ledger-core/src/audit.rs`, `crates/ledger-store/src/audit.rs`, `crates/ledger-mcp/src/tools/audit.rs`, `templates/workpaper.typ`, `crates/ledger-cli/tests/audit.rs`, `handbook/attestations-and-audit.md`; touches the 8.0.3 files, `tools/prompts.rs`, the CPA package builder.
6. **Tests and proofs:** `audit_is_deterministic`, `audit_detects_each_seeded_defect`, `amount_mismatch_never_counts`, `below_bar_not_counted`, `stale_head_period_attestation_refused`, `cpa_package_refuses_without_a_counted_audit_unless_a_reason_is_given`, `audits_wait_for_the_upgrade`, `workpaper_pdf_is_deterministic`; no kernel change (the audit calls kernel functions; it adds none); Lean: none (the laws it checks are already theorems).
7. **Done when:** (1) the code table has one row per check with its predicate, code, seeding fixture and severity, and `audit_detects_each_seeded_defect` iterates it; (2) a fixture with an unfooted cash flow, a missing receipt, an open draft and a duplicate blob produces exactly those findings; (3) the same head gives the same workpaper sha twice; (4) `export_cpa_package` refuses and the override is journaled; (5) 8.0.6 in the PR; (6) `just check` green.
8. **As built:** none yet.
8. **As built** (2026-10-29, branch `P8`; schema 20, taken at merge): every step built; departures:
- `ledger_core::audit::statements(ledger, input, through)` takes an `AuditInput` (the policy settings, the set of entries with a receipt, the cash flow's footing or why it could not be made, the settings problems, the undecided 1099 vendors) because a ledger cannot read attachments, the role settings or `ledger-config`; `settings_problems` stays in the server's helpers (it needs `ledger_config::Settings`) and the handler passes its strings. Predicates that a correct engine never lets happen (`check_trial_balance`, `check_equation`, `check_articulation`, `check_cash_flow`, `check_control`, `check_split`, `check_register`) are seeded directly; the rest are seeded through the ledger (`crates/ledger-core/tests/audit.rs`, not through `Store`: core cannot depend on the store). `close_split_drifts` tests each member's running total against its exact share within a minor unit, not by recomputing `kernel::apportion_cumulative`. `cash_flow_unfooted` also covers a statement the engine refuses.
- `legacy_reversal_exempt` did not exist as a name in code: it is the info finding for a reversal the policy would cover that has no receipt of its own.
- The integrity module is `crates/ledger-store/src/audit.rs`, a child module of `store` (it reads the store's private state). Backup standing is read by the server and handed in (`BackupStanding`, `backup_findings`); books with no backup target set up have no finding. Codes: `journal_damaged`, `projection_differs`, `blob_missing`, `blob_damaged`, `access_log_damaged`, `extraction_blob_missing`, `duplicate_blob`, `backup_stale`, `drill_failed`.
- The workpaper template is `crates/ledger-import/templates/workpaper.typ` with `crates/ledger-import/src/workpaper.rs` (that is where the other templates live), not `templates/workpaper.typ`. `audit.run` also carries `workpaper_pdf` (the JSON is `workpaper_attachment`). A run is idempotent on (through, figure head, scope, programme); `through_seq` is the figure head (the last line bearing on a figure: audits, attestations, evidence filings, decisions and the format declaration do not move it), not the journal head.
- `policy.attestation_bar` is one canonical text, `effort=high verifiers=2 adversarial=true`, not an object (the TOML codec has no table value); `Kind::AttestationBar`. `counted` is read when asked; a month needs the effort, a year-level run (annual, cpa_handoff, full) the whole bar.
- Scopes: `books.audit` previews at books:write and runs at books:post; the three attest tools are books:write; `books.workpaper` and `books.attestations` books:read. `books.attest_period` takes `period` as an id or a name. A receipt reading agrees when its amount is the entry's debit total or any one line, and its date the entry's.
- The CPA gate applies only on books that declared `audits`, so merging changes nothing on the live books before the go-ahead. `policy.cpa_package_requires_audit` unset means refuse once the books hold an audit run and warn before (not refuse from the start: every export of fresh books in ~30 existing tests would otherwise need a reason); the owner who wants it refused from the start sets `refuse`. `books.export_cpa_package` gained `without_audit_reason`.
- Needs attention: "Month-end attestation due", "Full audit due before the CPA handoff" and "Annual audit due", only for books that declared `audits`. The `audit` prompt (P5) gives the audit procedure; the attestation procedure is the handbook section `attestations-and-audit`.
- `period.attested` was the placeholder "unknown event" in `tests/format.rs` and `ledger-gui/tests/newer_journal.rs`; they use `ledger.not_yet_written` now.
- 8.0.6: `books_written_under_older_rules_still_open` hand-writes the four events; fixture `old-books/p8_before` (writer `p8_before.rs`, run against b97234ac); `audits_is_its_own_feature`; `books_p2_served_cut_over_whole` refuses `books.audit` and `books.review_entry` before consent. No figure moved, no golden changed. Merge does not need the owner's go-ahead; the live books refuse the new writes until he gives it.
- Fitted to P5 after it merged first: `next_steps.rs` (after `books.close_period` the steps are `books.audit` and `books.attest_period`; an audit's run leads to `books.workpaper`, the receipt and entry attestations and `books.attest_period`; the attest tools lead to `books.attestations` and `books.export_cpa_package`), the guard `audit_required` (remedy `books.audit`, also the refusal of a stale `books.attest_period`), the `audit` prompt names the audit tools, and the tool count is 140.
- No kernel, Kani or Lean change.
**Struck from P8:** ~~bar versioning (`counts` derived against the bar in force at that seq; `bar_change_is_prospective`), the Lean counting lemma~~ → IZ-3d; ~~"vault public-key sha"~~ → D-48; ~~"access-log tooling"~~ → `books.access_log` exists (P2); ~~`books.audit` run as a job~~ → D-53.
@@ -1619,7 +1639,7 @@ Each unit follows the 8.0.4 template. "A-n" is an owner answer numbered in 8.3.1
5. **Files:** owns `docs/runbooks/re-audit.md`, `crates/ledger-cli/tests/defects.rs`.
6. **Tests and proofs:** the one test.
7. **Done when (engineering):** the runbook exists and names only tools and commands that exist; the test passes. **Exit (the owner's run):** zero failing workpaper checks; cash flow difference 0.00; every receipt and posted entry carries a counted attestation and none disagrees; the access log shows every action under the right principal and no transcript names the data root; no result carried a full TIN to a client that should not see one (D-29: the agent's exports may); agent context on receipts stayed under 100K tokens per the harness's own token count; the sign-off journaled.
8. **As built:** none yet.
8. **As built:** (2026-10-29, branch `P10`.) `docs/runbooks/re-audit.md`: eight steps (the audit and its fixes; receipts attested; entries reviewed and periods attested; the defect regressions; the Sheet comparison; the CPA package from both doors; verify, backup drill and sign-off; the exit table), T1 and T2, every tool named is in the scopes table. `crates/ledger-cli/tests/defects.rs`: `every_defect_reproduction_is_a_named_test` (23 rows, defects 1 to 19 and 21 to 24), plus two the unit did not list: `defect_20_no_migration_tool_exists` and `the_re_audit_runbook_names_only_what_exists` (Done-when "names only tools that exist" needed a check). Departures: (a) **`books.inherit_evidence` does not exist** (only the event type is gated in `format.rs`; no tool, no scope row); the runbook links the original's receipt to the old reversal with `books.link_attachment`, which journals `attachment.linked` and satisfies the evidence rule, and `legacy_reversal_exempt` is informational so the audit is clean either way. If the owner wants the D-12 annotation as such, a tool is a later unit. (b) Defect 2 (closed by D-12/D-47) maps to `a_reversal_needs_no_receipt_of_its_own`'s rule; defect 20 has no test of its own and gets the no-tool check. (c) The Sheet workpaper is found with `books.find_attachment { text }` because `workpaper` is not a `books.file_document` category (the audit files its own). (d) No CI job added: `check` runs the new test. **Owner's, not checked here:** the run itself (zero failing checks, counted attestations, equal CPA packages, backup drill, sign-off, transcripts free of the data root, token counts).
#### P11a: packaging and updates (D-19, D-22, D-24, D-25, D-26)
@@ -1638,7 +1658,7 @@ Each unit follows the 8.0.4 template. "A-n" is an owner answer numbered in 8.3.1
5. **Files:** owns `crates/ledger-host/src/update.rs`, `deploy/packaging/*`, `.github/workflows/release.yml`, `docs/packaging.md`; touches `commands.rs`, `crates/ledger-gui/src/owner.rs`, `mise.toml`.
6. **Tests and proofs:** named above.
7. **Done when:** (1) on a clean Linux VM the AppImage installs, opens, onboards and posts an entry with no terminal and no admin password, and updates itself from a test feed; (2) on Arch, Debian and Fedora the packages install and upgrade with the head unchanged; (3) an update leaves every book's head and goldens unchanged and a bridge session carries on across it; (4) a backup made on one system restores on another; (5) macOS and Windows: the same as (1), once the signing prerequisites exist (record which are done in the As-built block); (6) `just check` green.
8. **As built:** (steps 0 to 3 as far as stated, 2026-10-29; steps 4 to 7 are later waves.) **Step 0, partly.** Built: the macOS login Keychain store (`apple-native-keyring-store` 1.0.2, feature `keychain`) and the Windows Credential Manager store (`windows-native-keyring-store` 1.1.0) behind `tokens.rs::Keeper`, on the same `Items` trait and the same laws as the Secret Service (`os` modules per OS; shared `refused`, `make_in`, `read_item`, `remove_item`); `NOT_YET` now only for other OSes; the ignored `os_keyring_keeps_and_forgets` compiles on all three; the `portable` job in `.github/workflows/check.yml` (`macos-latest` gating, `windows-latest` allowed to fail) builds `ledger-cli --no-default-features`, `cargo build --workspace --all-targets`, `cargo test --workspace` and the ignored keyring test. Checked here: Linux only (fmt, clippy, the tokens tests); no macOS/Windows target is installed, so both stores and the job are proven only by the first CI run. **Not built, and not a `cfg` gap: the Windows host.** Windows data root from the known-folder API with an owner-only DACL, named pipes with a SID check, `flock` and the Unix-only `host`, `start`, `bridge`, `opener` and `local`/`admin` sockets are a port of several crates (inventory in `docs/packaging.md`); macOS needs the CI run to show which Linux-only items (`seccomp` in `refuse.rs`, `renameat2`, `close_range`) need fences. So the step 0 Done-when ("setup ... gui work on each OS") is open for both. **Step 1 (the trial): not run in that wave**: it needs clean macOS, Windows and Linux machines; `mise.toml` now pins `cargo-packager` 0.11.8, `cargo-packager-updater` 0.2.3, `cargo-generate-rpm` 0.21.1 and `docs/packaging.md` holds the empty table and what is known. **Steps 1 (Linux half), 2 and 3 (2026-10-29, branch `P11a`).** *Pins:* the EM removed step 0's early pins; `mise.toml` now pins `cargo-packager` 0.11.8 and `cargo-generate-rpm` 0.21.1, which `just package-linux` calls; **`cargo-packager-updater` is a library, not a tool, so it is not pinned** (the plan's three-tool list was wrong on that); `update.rs` verifies with `minisign-verify` 0.3 (the crate the updater uses) and the tests sign with the `minisign` 0.10 dev-dependency. *Step 1:* `docs/packaging.md` has the Linux rows filled from real builds (47 MB .deb, 36 MB .rpm, 44 MB AppImage, the pacman tarball and PKGBUILD) and eight findings; the `.dmg` and NSIS rows stay "(trial)" (need a Mac and Windows; Windows is P11c). The findings that shaped the build: a binary linked on this machine (glibc 2.44) does not start on Debian 13, so packages are built on `ubuntu-24.04` in CI and in `rust:1.99-trixie` locally (`just release-trixie`); the books refuse to run under Docker's default seccomp filter (tests use `seccomp=unconfined`); an AppImage's launcher appends arguments to the desktop file's `Exec`, so the desktop files say plain `wanderbooks` and a bare launch with no terminal is the viewer (`main.rs::is_bare_launch`); the AppImage needs a square icon (**a placeholder PNG is committed; the owner should replace it**). *Step 2:* `crates/ledger-host/src/update.rs` (`update`, `rollback`) and the two CLI modes: a manifest (version; per platform `url`, `sha256`, `signature` = base64 of the minisign `.sig`, the shape cargo-packager signs in), accepted only if higher, sha256 equal AND signature valid against `WANDERBOOKS_RELEASE_PUBKEY` (compiled in; **no build carries one until step 5**, so today a build updates only from a `file://` feed with `--trusted-key`); stage under `<root>/updates/<version>/`, keep the running version under `updates/previous/` with the journal format it knew, swap by renaming a complete copy made beside the target, `--help` health check with automatic put-back; `rollback` refuses when a book declares a format or feature the kept version lacks; a package marks itself with `share/wanderbooks/install-method` and then both refuse. The restart is the host's existing one (nothing new in `host.rs`). Tests: `crates/ledger-cli/tests/update.rs` (`update_is_atomic`, `failed_update_rolls_back`, `unsigned_update_refused`, `bridge_survives_update`, `update_waits_for_a_running_call`, `package_managed_install_refuses_update`, `rollback_refuses_after_a_book_consented_to_a_newer_format`, `an_update_replaces_the_program_keeps_the_old_one_and_rollback_brings_it_back`) all pass (8 in 12 s); five unit tests in `update.rs`. *Step 3:* `deploy/packaging/` (`packager.toml`, the rpm table in `crates/ledger-cli/Cargo.toml`, `aur/PKGBUILD`, `build-linux.sh`, `repo-indexes.sh`, `local-release.sh`, `test-linux-packages.sh`), the `just` recipes `package-linux`, `local-release`, `release-trixie`, `test-linux-packages`, and `.github/workflows/release.yml` (Linux packages on a `v*` tag, attached to the release, apt and dnf indexes pushed to `gh-pages`; untested, GitHub only). `linux_packages_install_and_upgrade` is `test-linux-packages.sh`: install 0.1.0 from an apt, a dnf repository and `makepkg` over the AUR PKGBUILLine truncated
8. **As built:** (steps 0 to 3 as far as stated, 2026-10-29; steps 4 to 7 are later waves.) **Step 0, partly.** Built: the macOS login Keychain store (`apple-native-keyring-store` 1.0.2, feature `keychain`) and the Windows Credential Manager store (`windows-native-keyring-store` 1.1.0) behind `tokens.rs::Keeper`, on the same `Items` trait and the same laws as the Secret Service (`os` modules per OS; shared `refused`, `make_in`, `read_item`, `remove_item`); `NOT_YET` now only for other OSes; the ignored `os_keyring_keeps_and_forgets` compiles on all three; the `portable` job in `.github/workflows/check.yml` (`macos-latest` gating, `windows-latest` allowed to fail) builds `ledger-cli --no-default-features`, `cargo build --workspace --all-targets`, `cargo test --workspace` and the ignored keyring test. Checked here: Linux only (fmt, clippy, the tokens tests); no macOS/Windows target is installed, so both stores and the job are proven only by the first CI run. **Not built, and not a `cfg` gap: the Windows host.** Windows data root from the known-folder API with an owner-only DACL, named pipes with a SID check, `flock` and the Unix-only `host`, `start`, `bridge`, `opener` and `local`/`admin` sockets are a port of several crates (inventory in `docs/packaging.md`); macOS needs the CI run to show which Linux-only items (`seccomp` in `refuse.rs`, `renameat2`, `close_range`) need fences. So the step 0 Done-when ("setup ... gui work on each OS") is open for both. **Step 1 (the trial): not run in that wave**: it needs clean macOS, Windows and Linux machines; `mise.toml` now pins `cargo-packager` 0.11.8, `cargo-packager-updater` 0.2.3, `cargo-generate-rpm` 0.21.1 and `docs/packaging.md` holds the empty table and what is known. **Steps 1 (Linux half), 2 and 3 (2026-10-29, branch `P11a`).** *Pins:* the EM removed step 0's early pins; `mise.toml` now pins `cargo-packager` 0.11.8 and `cargo-generate-rpm` 0.21.1, which `just package-linux` calls; **`cargo-packager-updater` is a library, not a tool, so it is not pinned** (the plan's three-tool list was wrong on that); `update.rs` verifies with `minisign-verify` 0.3 (the crate the updater uses) and the tests sign with the `minisign` 0.10 dev-dependency. *Step 1:* `docs/packaging.md` has the Linux rows filled from real builds (47 MB .deb, 36 MB .rpm, 44 MB AppImage, the pacman tarball and PKGBUILD) and eight findings; the `.dmg` and NSIS rows stay "(trial)" (need a Mac and Windows; Windows is P11c). The findings that shaped the build: a binary linked on this machine (glibc 2.44) does not start on Debian 13, so packages are built on `ubuntu-24.04` in CI and in `rust:1.99-trixie` locally (`just release-trixie`); the books refuse to run under Docker's default seccomp filter (tests use `seccomp=unconfined`); an AppImage's launcher appends arguments to the desktop file's `Exec`, so the desktop files say plain `wanderbooks` and a bare launch with no terminal is the viewer (`main.rs::is_bare_launch`); the AppImage needs a square icon (**a placeholder PNG is committed; the owner should replace it**). *Step 2:* `crates/ledger-host/src/update.rs` (`update`, `rollback`) and the two CLI modes: a manifest (version; per platform `url`, `sha256`, `signature` = base64 of the minisign `.sig`, the shape cargo-packager signs in), accepted only if higher, sha256 equal AND signature valid against `WANDERBOOKS_RELEASE_PUBKEY` (compiled in; **no build carries one until step 5**, so today a build updates only from a `file://` feed with `--trusted-key`); stage under `<root>/updates/<version>/`, keep the running version under `updates/previous/` with the journal format it knew, swap by renaming a complete copy made beside the target, `--help` health check with automatic put-back; `rollback` refuses when a book declares a format or feature the kept version lacks; a package marks itself with `share/wanderbooks/install-method` and then both refuse. The restart is the host's existing one (nothing new in `host.rs`). Tests: `crates/ledger-cli/tests/update.rs` (`update_is_atomic`, `failed_update_rolls_back`, `unsigned_update_refused`, `bridge_survives_update`, `update_waits_for_a_running_call`, `package_managed_install_refuses_update`, `rollback_refuses_after_a_book_consented_to_a_newer_format`, `an_update_replaces_the_program_keeps_the_old_one_and_rollback_brings_it_back`) all pass (8 in 12 s); five unit tests in `update.rs`. *Step 3:* `deploy/packaging/` (`packager.toml`, the rpm table in `crates/ledger-cli/Cargo.toml`, `aur/PKGBUILD`, `build-linux.sh`, `repo-indexes.sh`, `local-release.sh`, `test-linux-packages.sh`), the `just` recipes `package-linux`, `local-release`, `release-trixie`, `test-linux-packages`, and `.github/workflows/release.yml` (Linux packages on a `v*` tag, attached to the release, apt and dnf indexes pushed to `gh-pages`; untested, GitHub only). `linux_packages_install_and_upgrade` is `test-linux-packages.sh`: install 0.1.0 from an apt, a dnf repository and `makepkg` over the AUR PKGBUILLine truncated
#### P11b: the agent dock and books homes (D-27)
@@ -1667,7 +1687,7 @@ Each unit follows the 8.0.4 template. "A-n" is an owner answer numbered in 8.3.1
5. **Files:** owns the Windows arms of the files above; touches `docs/packaging.md` (the inventory rows are struck as each lands), `docs/architecture.md`, `docs/testing.md`, `README.md` (the Windows caveat goes).
6. **Tests and proofs:** the existing root, lock, lease and transport tests run on the Windows job (`cargo test --workspace` there); `a_second_user_cannot_open_the_pipe` (new, Windows-only, `crates/ledger-host`); no kernel, Kani or Lean change.
7. **Done when:** (1) the `portable (windows-latest)` job is green with `experimental: false`; (2) `wanderbooks setup`, `books create`, `connect claude`, a bridge session, an upload and `wanderbooks gui` work on Windows from a source build (the engineer or the owner on a Windows machine; recorded in the As-built block); (3) `docs/packaging.md`'s Unix-only inventory is empty.
8. **As built:** (2026-10-29, branch `P11c`; written on Linux, type-checked and linted for `x86_64-pc-windows-msvc` with the target pinned in `mise.toml`, **not run on Windows**: the `portable (windows-latest)` job's first run is the evidence for everything marked unverified here, and Done-when 2 needs a person on a Windows machine.) **Step 1, the data root:** `ledger-store/src/winsec.rs (new, Windows only)` holds the Windows FFI (dependency `windows-sys` 0.61, `cfg(windows)`): `set_owner_only` (a protected DACL of one allow entry for the account's SID, inherited by what a folder holds), `is_private` (owner is the account or Administrators; no allow entry for anyone but the account, SYSTEM or Administrators; a missing DACL is not private; any failure is "no"), `current_user`/`process_user`/`pid_is_us`, the shell's known folders (`profile_folder`, `local_app_data`), `rename_new` (`MoveFileExW` without the replace flag, write-through) and the pipe process-id calls. `root.rs`: `account_home` is the profile known folder; `platform_data_dir(home)` is the shell's `LocalAppData` for the account's own home and `home\AppData\Local` for any other (tests); `set_mode`, `check_private_dir` (now takes the path), `recorded_root` and `rename_new` have their Windows arms beside the Unix ones. **Step 2, locking:** `ledger-store/src/lock.rs (new)` is the one seam, on std's `try_lock`/`try_lock_shared` (flock; `LockFileEx`), replacing the only `libc::flock` (`ops.rs`); the plan's "LockFileEx behind lock.rs" is std's, so there is no second `unsafe` copy. **Step 3, the transport:** `ledger-mcp/src/ipc.rs (new)` is the seam for both families: `Endpoint`, `Listener`, `connect`, `connect_std` (a blocking client with a read limit; on Windows polled with `PeekNamedPipe`, since a pipe read cannot be cut short), `Peer` (`peer_is_us`, `peer_pid`), `stop_signals`, `split_accepted`/`split_connected`. A Windows pipe is `\\.\pipe\wanderbooks-<sha256 of the canonical, lower-cased run/ folder, 8 bytes>-<name>`, made with an owner-only DACL, remote clients refused, `first_pipe_instance` once, and 16 idle instances kept waiting (each its own task, replaced on connect) so a client never finds all busy; clients open with `SECURITY_IDENTIFICATION`; both ends check the other's process token user against their own SID. `local.rs`, `admin.rs`, `host.rs`, `opener.rs`, `bridge.rs`, `commands.rs` use `ipc` (their `peer_uid`/`peer_pid`/`own_uid` moved into it); `ledger-host/src/channel.rs (new)` gives reqwest the Unix socket or `windows_named_pipe`. `lib.rs` of `ledger-host` and `ledger-mcp` and `main.rs` of `ledger-cli` lost every `cfg(unix)` gate and the "comes with P11" stubs. `start.rs`: the Unix start (`setsid`, `umask`, systemd-run, `XDG_RUNTIME_DIR`) is `cfg(unix)`; the Windows start is `DETACHED_PROCESS | CREATE_NEW_PROCESS_GROUP | CREATE_BREAKAWAY_FROM_JOB` (without the last when the job refuses it) with a fixed environment (`host_env`); `host.rs` told a replaced program by (size, last write) on Windows; `close_range` and `seccomp` stay Linux-only (D-32). Also: `backup.rs` stops a quiet git with `taskkill /T` and finds Git for Windows; `tokens.rs` and the viewer's thumbnail folder take the DACL; `.gitattributes` pins LF (autocrlf would break the byte-for-byte golden and hash tests on the runner). **Tests:** see `docs/testing.md` "P11c": `ledger-store/tests/windows_root.rs`, `ledger-mcp/src/ipc.rs` (4 on every OS), `ledger-host/tests/windows_pipe.rs::a_second_user_cannot_open_the_pipe`, Windows variants in `start.rs` and `host.rs`; the whole over-the-wire suite of `ledger-cli` now compiles for Windows and runs there through `common::clean_env`/`private_folder`/`default_root`/`mcp_http`. **Checked here:** `cargo check` and `cargo clippy -D warnings` for `x86_64-pc-windows-msvc` over the workspace and all targets (C dependencies stubbed with a no-op compiler/archiver shim, which `check` allows); on Linux fmt, clippy on the crates touched and their tests. **Not verifiable here, so the first CI run decides:** everything that runs on `windows-latest`, in particular the pipe code, the DACL reads, `taskkill`, the host's detached start and the over-the-wire suite. **Answers I chose** (alternatives in the report to the EM): the Windows ancestors check is none (CHOICE); the bridge's scripted-server tests (`against_a_server`) stay `cfg(unix)`; vault and TIN-registry files rely on inheritance from the private folder they sit in. **Left open:** Done-when 2 (a person on Windows runs setup, create, connect, a bridge session, an upload, `gui`), and Done-when 1 until CI shows the job green.
8. **As built:** (2026-10-29, branch `P11c`; written on Linux, type-checked and linted for `x86_64-pc-windows-msvc` with the target pinned in `mise.toml`, **not run on Windows**: the `portable (windows-latest)` job's first run is the evidence for everything marked unverified here, and Done-when 2 needs a person on a Windows machine.) **Step 1, the data root:** `ledger-store/src/winsec.rs (new, Windows only)` holds the Windows FFI (dependency `windows-sys` 0.61, `cfg(windows)`): `set_owner_only` (a protected DACL of one allow entry for the account's SID, inherited by what a folder holds), `is_private` (owner is the account or Administrators; no allow entry for anyone but the account, SYSTEM or Administrators; a missing DACL is not private; any failure is "no"), `current_user`/`process_user`/`pid_is_us`, the shell's known folders (`profile_folder`, `local_app_data`), `rename_new` (`MoveFileExW` without the replace flag, write-through) and the pipe process-id calls. `root.rs`: `account_home` is the profile known folder; `platform_data_dir(home)` is the shell's `LocalAppData` for the account's own home and `home\AppData\Local` for any other (tests); `set_mode`, `check_private_dir` (now takes the path), `recorded_root` and `rename_new` have their Windows arms beside the Unix ones. **Step 2, locking:** `ledger-store/src/lock.rs (new)` is the one seam, on std's `try_lock`/`try_lock_shared` (flock; `LockFileEx`), replacing the only `libc::flock` (`ops.rs`); the plan's "LockFileEx behind lock.rs" is std's, so there is no second `unsafe` copy. **Step 3, the transport:** `ledger-mcp/src/ipc.rs (new)` is the seam for both families: `Endpoint`, `Listener`, `connect`, `connect_std` (a blocking client with a read limit; on Windows polled with `PeekNamedPipe`, since a pipe read cannot be cut short), `Peer` (`peer_is_us`, `peer_pid`), `stop_signals`, `split_accepted`/`split_connected`. A Windows pipe is `\\.\pipe\wanderbooks-<sha256 of the canonical, lower-cased run/ folder, 8 bytes>-<name>`, made with an owner-only DACL, remote clients refused, `first_pipe_instance` once, and 16 idle instances kept waiting (each its own task, replaced on connect) so a client never finds all busy; clients open with `SECURITY_IDENTIFICATION`; both ends check the other's process token user against their own SID. `local.rs`, `admin.rs`, `host.rs`, `opener.rs`, `bridge.rs`, `commands.rs` use `ipc` (their `peer_uid`/`peer_pid`/`own_uid` moved into it); `ledger-host/src/channel.rs (new)` gives reqwest the Unix socket or `windows_named_pipe`. `lib.rs` of `ledger-host` and `ledger-mcp` and `main.rs` of `ledger-cli` lost every `cfg(unix)` gate and the "comes with P11" stubs. `start.rs`: the Unix start (`setsid`, `umask`, systemd-run, `XDG_RUNTIME_DIR`) is `cfg(unix)`; the Windows start is `DETACHED_PROCESS | CREATE_NEW_PROCESS_GROUP | CREATE_BREAKAWAY_FROM_JOB` (without the last when the job refuses it) with a fixed environment (`host_env`); `host.rs` told a replaced program by (size, last write) on Windows; `close_range` and `seccomp` stay Linux-only (D-32). Also: `backup.rs` stops a quiet git with `taskkill /T` and finds Git for Windows; `tokens.rs` and the viewer's thumbnail folder take the DACL; `.gitattributes` pins LF (autocrlf would break the byte-for-byte golden and hash tests on the runner). **Tests:** see `docs/testing.md` "P11c": `ledger-store/tests/windows_root.rs`, `ledger-mcp/src/ipc.rs` (4 on every OS), `ledger-host/tests/windows_pipe.rs::a_second_user_cannot_open_the_pipe`, Windows variants in `start.rs` and `host.rs`; the whole over-the-wire suite of `ledger-cli` now compiles for Windows and runs there through `common::clean_env`/`private_folder`/`default_root`/`mcp_http`. **Checked here:** `cargo check` and `cargo clippy -D warnings` for `x86_64-pc-windows-msvc` over the workspace and all targets (C dependencies stubbed with a no-op compiler/archiver shim, which `check` allows); on Linux fmt, clippy on the crates touched and their tests. **Not verifiable here, so the first CI run decides:** everything that runs on `windows-latest`, in particular the pipe code, the DACL reads, `taskkill`, the host's detached start and the over-the-wire suite. **Answers I chose** (alternatives in the report to the EM): the Windows ancestors check is none (CHOICE); the bridge's scripted-server tests (`against_a_server`) stay `cfg(unix)`; vault and TIN-registry files rely on inheritance from the private folder they sit in. **Second CI run (Windows):** `spec_vault` read every file under a book, lock files included; a held `LockFileEx` lock is mandatory, so the read failed (os error 33). The seam already locks dedicated files (`wanderbooks.lock`, `viewer.lock`, `server.lock`), never data files, so the lock-file approach was already in place; the tests now skip `*.lock` files (they hold no data). `update`/`rollback` and the host's update poll are `cfg(unix)` (P11a step 5 is Windows' updater). **Left open:** Done-when 2 (a person on Windows runs setup, crLine truncated
#### P12: max testing, again (D-33)
@@ -1697,14 +1717,15 @@ Each unit follows the 8.0.4 template. "A-n" is an owner answer numbered in 8.3.1
5. **Files:** owns `bookspec/**`; touches `crates/ledger-core/tests/bookspec.rs`, `crates/ledger-package/tests/package.rs`, `docs/spec/rules.md` (ids), `Cargo.toml` (workspace members if the checker joins the workspace).
6. **Tests and proofs:** `every_bookspec_example_holds` unchanged; `bookspec/checker` tests (the schema, the derivations); `bookspec/lean` builds with standard axioms only.
7. **Done when:** (1) the checker crate exists and `just check` still checks all 56+ examples; (2) the `Engine` trait and the wanderbooks adapter run the runner green for every claimed rule; (3) every B-rule has an id, statement, source and example; (4) the five laws are theorems in `bookspec/lean`; (5) the GnuCash adapter runs and its differences are settled from the sources (recorded in the As-built block).
8. **As built:** steps 1-3 landed 2026-10-26 (`579d0eed`); steps 4 and 5 built 2026-10-29 on branch `P13` (step 6 not started: it waits on the owner, see below).
8. **As built:** steps 1-3 landed 2026-10-26 (`579d0eed`); steps 4 and 5 built 2026-10-29 on branch `P13` and step 6 on 2026-10-29 (the repository of its own, below).
- **Step 4, the text.** `bookspec/spec/` holds `00-model.md` (definitions, the model, the five laws, how an example is checked) and one file per area (`entry`, `ledger`, `statement`, `period`, `partner`, `asset`, `prepaid`, `revenue`, `arap`, `1099`, `format`), 667 lines of the 3,300 a 60-page edition allows. Every B-rule has a section `### BS-<area>-<n> (B-n)` with status, MUST/SHOULD/MAY statement, sources, symbolic form, examples and applicability; B-39, B-40, B-43, B-45 are reserved sections (allocated, not yet stated in `rules.md`); B-44, stated in `rules.md`, now carries its `bookspec:` line and CONVENTION status (catalogue and index updated). `checker/tests/spec.rs` fails the build on a missing section, a statement without a modal verb, a status that differs from the catalogue, examples that differ from the catalogue, a vendored pin that is not verbatim in its source (whitespace and curly quotes normalized), or more than 60 pages.
- **The five laws** are Lean theorems `Bookspec.law_debits_equal_credits`, `law_accounting_equation`, `law_trial_balance`, `law_net_income_articulates`, `law_cash_flow_is_change_in_cash` in `bookspec/lean` (Basic, Entry, Statements, Closing, Ledger ported from `verify/lean` under the namespace `Bookspec`; `Laws.lean` states them; `Audit.lean` prints their axioms: `propext`, `Quot.sound`, `Classical.choice` only). `checker/tests/lean.rs` builds and audits when Lean is installed.
- **New examples (8)**, each figure worked by hand in the file: `entry-an-unbalanced-entry-is-refused`, `entry-a-line-amount-is-positive`, `entry-cents-add-exactly`, `ledger-a-fraction-of-a-cent-is-refused`, `ledger-a-child-account-has-its-parents-type`, `ledger-a-mistake-is-reversed-never-erased`, `statement-the-books-balance-and-the-equation-holds`, `period-a-close-sweeps-the-year-into-capital` (66 in all). Rules now with an example: B-1 to B-12 and B-21, B-23 (`arap-hand-control-posting-refused` also shows it). New refusal words: `unbalanced`, `too_few_lines`, `negative_amount`, `zero_amount`, `finer_than_minor_unit`, `parent_type_mismatch`, `already_reversed`, `period_closed`; a refused entry may carry a non-positive amount. Finding while writing: `close ... to = "retained_earnings"` posts to the role account 3999 Retained Earnings, not to a setup capital account; the example says so.
- **Step 5, the derivation.** `bookspec/checker/src/derive.rs` is an engine-free reference model (exact decimals) that replays entries, reversals, a close to retained earnings, bills and invoices, derives `trial_balance`, `balances`, `profit_and_loss` and `entry` figures plus the law checks (books net to zero, A = L + E + (R - X)) and checks each refusal against its rule; everything else is reported `Skipped`, never passed. `checker/tests/derive.rs`: no example disagrees; the eight law examples are derived in full; a figure moved by a cent and an unjustified refusal are both caught.
- **The runner against wanderbooks** (`the_runner_reports_every_claimed_rule_green`) is green for every claimed rule, now including BS-entry-1 to -3, BS-ledger-1 to -6 and -8, BS-statement-1 and -2, BS-period-1 and -2.
- **GnuCash, the second engine** (`bookspec/gnucash`, ignored tests; needs the `just oracle` image). It claims the rules whose examples ask only for balances. Passes BS-entry-3, BS-ledger-3, BS-statement-1, BS-statement-2. Three differences, settled from the sources and pinned by `gnucash/tests/gnucash.rs`: GnuCash books an unbalanced entry and parks the difference in `Imbalance-USD` (BS-entry-1 refuses it: Pacioli ch. 36, Pub. 583); it reads a credit of minus 100.00 as a debit (BS-entry-2 is a CONVENTION that removes the ambiguity); it rounds 10.005 to 10.01 silently (BS-ledger-1's CHOICE, found by the first cross-check, is not to). The examples stand in each case. GnuCash has no reversal link, so BS-ledger-5, -6 are not claimed by it.
- **Not done, and why.** (a) Done when 3 is met for the rules the schema can show; these stated rules still have no example because the schema has no event or query for them: BS-entry-4 (B-28), BS-ledger-7 (B-20), -9 (B-29), -12 (B-44), BS-asset-2 (B-22), -3 (B-24), BS-revenue-1 (B-17), BS-arap-1 (B-18), BS-arap-6 (B-42, whose allocated names `arap-settlement-exact` and `processor-fee-gross-presentation` wait on a payment/settlement event and an aging query), BS-format-1 (B-27); each section says why. B-39, B-40, B-43, B-45 are unstated in `rules.md`. (b) Step 6, the repository of its own, is the owner's call (a public repository, CC BY 4.0 for the text) and waits on step 5 being stable; nothing was pushed. (c) The derivation covers the ledger-core examples; depreciation, schedules, members, 1099s, formats and the cash-flow sections are skipped by the model and checked by the engines only.
- **Not done, and why.** (a) Done when 3 is met for the rules the schema can show; these stated rules still have no example because the schema has no event or query for them: BS-entry-4 (B-28), BS-ledger-7 (B-20), -9 (B-29), -12 (B-44), BS-asset-2 (B-22), -3 (B-24), BS-revenue-1 (B-17), BS-arap-1 (B-18), BS-arap-6 (B-42, whose allocated names `arap-settlement-exact` and `processor-fee-gross-presentation` wait on a payment/settlement event and an aging query), BS-format-1 (B-27); each section says why. B-39, B-40, B-43, B-45 are unstated in `rules.md`. (b) Step 6 is done, see the next bullet. (c) The derivation covers the ledger-core examples; depreciation, schedules, members, 1099s, formats and the cash-flow sections are skipped by the model and checked by the engines only.
- **Step 6, the repository of its own (D-34, D-65 as amended: Gitea only).** `https://git.swaits.com/swaits/bookspec` is the public home (no GitHub repository, none planned; GitHub's token could not create one and the owner chose Gitea). `git subtree split --prefix=bookspec` kept the history of `bookspec/` (539 commits). The new tree adds `LICENSE-MIT`, `LICENSE-APACHE` (the code), `LICENSE-CC-BY-4.0` (`spec/` and `examples/`), a README licence table, `.github/workflows/check.yml` (fmt, clippy, rustdoc, `cargo test --workspace`; the two Docker-gated GnuCash tests stay ignored) and `repository` metadata. `bookspec_checker::examples()` and `spec_files()` (`include_dir` over the crate's `../examples` and `../spec`) carry the files, so no consumer needs a path. wanderbooks pins `bookspec-checker` and `bookspec-runner` as git dev-dependencies of `ledger-core` (and the checker of `ledger-package`) at `rev = 74f2853d5f0db0066147c17fed8033cfe3d83a24`; `bookspec/` is deleted here, the four `just check` lines for it are gone, and `just check` still runs every example through `cargo test -p ledger-core --test bookspec`. The two checks that read this repository moved here: `every_vendored_pin_is_verbatim` (the pins against `docs/spec/sources`) and `rules_md_carries_every_id` (both in `crates/ledger-core/tests/bookspec.rs`); bookspec keeps the citations' form check. `bookspec/lean` and the GnuCash adapter went with the repository. Raising the pin is a one-line `rev` change in two `Cargo.toml` files.
### 8.3 P4b reference design: receivables and payables, first class (D-45)
@@ -2162,6 +2183,8 @@ The owner decisions of 2026-10-16 (at the top of this document) replace the form
| D-61 | Q-9 answered (owner, 2026-10-29, the question tool: "Split the reader out later") | **The reader (`extract.rs`) moves into a small library crate of its own, `ledger-reader`, that `ledger-store` and `ledger-import` both depend on; a half-day unit (U0d) for the implementing team after S4.5.** Until then the arrow stands as built; `ledger-import` never depends on `ledger-store`. | U0d; `docs/architecture.md` |
| D-62 | Native Windows (owner, 2026-10-29, the question tool, after P11a step 0 found the Windows side is a port, not cfg fixes: "macOS first, Windows port as its own unit") | **The first bundled app ships for macOS and Linux; the native Windows port is unit P11c**, built after U0e beside P11b; Windows stays WSL2-only meanwhile and the `portable (windows-latest)` job is allowed to fail until P11c lands. D-56's destination is unchanged, only its order. | P11a, P11b, P11c |
| D-63 | Where agents' tokens live (owner, 2026-10-29, the question tool, on seeing P11a step 0 add Keychain and Credential Manager stores: "why are you talking about the OS keyring? I thought we made a decision to not use that garbage"; the plan had no such decision recorded, so this one records it) | **A 0600 file under the 0700 data root, on every OS; no OS keyring.** The data root is the trust boundary already (I-1: hidden, not locked, same user), so the keyring added no wall and cost headless servers a D-Bus session, CI a knob, and every OS its own store. Unit U0e removes the Secret Service, Keychain and Credential Manager stores and the `file_keyring` knob; 3.3's sentence is struck. | U0e, P9a's recipe, P11a, P11c |
| D-64 | Q-4 answered (owner, 2026-10-29, the question tool: "Defer: ship unsigned builds to yourself first") | **P11a step 5 (the signed release list, the key ceremony, Apple and Microsoft code signing) is deferred until the first public release is near.** CI builds unsigned artifacts for the owner's own machines; `wanderbooks update` works from a local feed meanwhile (no compiled-in key). The two-key design of D-25 is unchanged, only its date. | P11a step 5, `release.yml` |
| D-65 | P13 step 6 (owner, 2026-10-29, the question tool: "Split it out now") | **The bookspec specification gets a public repository of its own now**, with this repository depending on it; the ten stated rules without examples and the three reserved numbers are published as such. | P13 step 6 |
### 10.2 Deferred
@@ -2172,7 +2195,7 @@ The owner decisions of 2026-10-16 (at the top of this document) replace the form
**Open (2026-10-29): Q-4, Q-6, Q-7, each when its unit starts.** One at a time, in plain English; none blocks the units before it. Q-5, Q-8 and Q-9 were answered (D-57, D-58, D-61) and are kept below for the record. Q-5 and Q-8 were answered on 2026-10-28 (D-57, D-58) and are kept below for the record.
**Q-4 (before P11a step 5).** Releases: should the app check updates against our own signed release list as well (two keys, one on your hardware token and one offline, a version that only goes up, an expiry), as decided on 2026-10-18 (D-25), or is the packaging tool's own single-key signature plus Apple's and Microsoft's code signing enough? The second is simpler and already refuses an older version; the first also stops a frozen or replayed update list. Recommended: the first, as decided, since the key work is small once the token exists.
**Q-4 (closed 2026-10-29 → D-64: deferred; unsigned builds first).** Releases: should the app check updates against our own signed release list as well (two keys, one on your hardware token and one offline, a version that only goes up, an expiry), as decided on 2026-10-18 (D-25), or is the packaging tool's own single-key signature plus Apple's and Microsoft's code signing enough? The second is simpler and already refuses an older version; the first also stops a frozen or replayed update list. Recommended: the first, as decided, since the key work is small once the token exists.
**Q-5 (closed 2026-10-28 → D-57: once, after everything).** The receivables and payables work needs one more go-ahead to switch your books to the new format (numbers, terms, line accounts, sent records, discounts, write-offs). Should we ask once, after everything is built (write-offs and discounts included), or earlier, after invoice numbers and terms land, with a second ask later? Recommended: once, after everything, unless you need numbered invoices sooner.