feat(audit): books.audit, the attestation tools and the CPA gate (P8)

The whole re-audit through the tools: books.audit previews its checks, then
with the preview's digest runs them, files the workpaper and journals the
run; the same books, day and scope return the run that exists.
books.attest_receipt, books.review_entry and books.attest_period record a
named client's claim with its self-reported method; books.attestations
weighs each against policy.attestation_bar as it stands. Under
policy.cpa_package_requires_audit = refuse, books that declared audits
cannot export the CPA package without a counted audit for the handoff at the
current head unless a reason is given, which is journaled as a decision
(D-9). Needs attention asks for the month-end attestation, the annual audit
and the audit before the handoff. Rules B-45, the handbook section, the
interface, parity, receipt-store and testing records, and the As-built
block. Merge needs no go-ahead: the live books refuse the new writes until
the owner gives it.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HS8WZziDnyxunC23MUxrLp
This commit is contained in:
swaitsandClaude Fable 5.1 committed 2026-10-29 15:54:26 -06:00
1 parent d45e842873
commit 45bcce679b
23 files changed
+2128 -22

No files matched your search

+12 -1
View File
@@ -1589,7 +1589,18 @@ 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. `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) was not written: P5 has not merged and `tools/prompts.rs` does not exist; the 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.
- 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.