Files
wanderbooks/plan/review-2026-10-26/intermediate_zoom_out_report.md
T

42 lines
11 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Stage 8: Intermediate zoom-out on the proposed mitigations (`plan/served.md` §8, 971-1889)
Reviewer: `egm-zoom-out`, 2026-10-26, having read the Stage 5-7 reports and the code they cite. Judges the proposed fixes before they are applied.
## 0. The one change that dissolves most of the design blockers: drop "reversal as a draft"
**Blocker. P4 971-993; I-4/I-5 (64-65); 4.9 513; 5.2 574; DC-A2, DC-A3, DC-B3, ST-F4, comprehension F1/F3/F5/E5.**
No owner decision asks for a reversal to be a draft: top decision 5 and D-12 say old reversals borrow the original's receipt; the live handbook and `ReceiptPolicy` say reversals need none; S1 built `void_document` as an immediate reversal and the owner upgraded to it. A reversal stays previewed-then-posted in one call, every reversal check at the moment it posts (what `check_reversal` already does), evidence = the original's by rule; evidence a reversal genuinely needs of its own (a returned-item notice for a bounce) is attached right after, as every at-once tool works today. Delete from P4: `entry.reversal_drafted`, `Step.draftReversal`, `evidence: link_original|{ids}|later`, the "reversal draft needs evidence" attention item, `reversal_needs_evidence`. Rewrite I-4: "Corrections are posted reversals; a reversal's evidence is its original's." `inherit_evidence` becomes an audit annotation only (D-12 names it: keep as a one-line event for the re-audit).
## 1. The critic's mechanisms, one by one
- **1a `settlement.posted` (DC-A4): over-engineered.** Replay already tags "the first untagged control line for `amount`" (ledger.rs:6466). Simpler: add `line` (and `discount_line`) indices to the existing `invoice.payment_received`/`bill.paid` events under `settlements`; the tool writes lines in a fixed order (payment lines in application order, then discount lines).
- **1b `Movement::Recovery` + role (DC-A11): YAGNI.** Handbook rule: same tax year, reverse the write-off then `receive_payment`; prior year, a plain entry Dr deposit / Cr "Bad debt recoveries" (revenue, tax category `other_income`), memo naming the invoice; the document stays Written off. Record as a decision.
- **1c `receive_payment.excess_to` (DC-A6): contradicts answer 5 and is unnecessary.** Replacement-before-bounce works today: park the cash Dr 1000 / Cr 2300 (no role, so allowed); reverse payment 1 dated the bounce day; `receive_payment {deposit_to: 2300, date ≥ bounce}` (`check_payment_account` accepts a role-less liability; bounds pass). Write it as a handbook procedure.
- **1d discounts on asset-line bills (DC-A7): take the second option.** Refuse a discount on a bill with an asset or prepaid line, remedy in the error (record the bill net, or reclassify Dr 6995 / Cr 1500). Record the decision.
- **1e the fee (DC-A5): Blocker on the document, Major on the scope.** The fee goes in S6. One kernel identity check with Kani (Σcash + Σdiscount == Σapplication; deposit == Σcash − fee); do not refactor `payout_plan`. Copy the payout pattern for the fee line (same expense account payouts use). `receive_payment {…, fee?: {amount, account?}}`, one posting row, behind `settlements`.
- **1f `adopt_control_line` variants (DC-A8): defer the whole tool.** P4b.14 item 4 already gives zero-code remedies (reverse and re-enter; or leave it with a decision and a CPA README note, which the identity's `unattached` term exists for). Build it only if `books.control_check` on the live books lists a line neither remedy can take. Same for `assign_role move_balance` (B-23 already says a control role with a balance stays). S7 shrinks to `write_off_invoice` + `bad_debts` role + B-41.
- **1g `owed_on` per-account identity (DC-A9): falls away with 1f.**
- **1h evidence gate (DC-A10/B2): right direction, four times too big.** Three moves: (1) move the policy evaluation from `ledger-config` into `ledger-core` as one function over `(&Ledger, &JournalEntry, &Settings)`; exemptions by origin use what `Ledger` already knows (closing entries, schedule postings, depreciation runs); (2) `Store::post_entry` calls it; (3) the GUI calls the same function over its Snapshot and deletes the string match at books.rs:708. At-once tools stay "post at once, attach evidence right after" with the existing `missing_receipt` item; the invoice carries its PDF and the bill its upload anyway (S4).
- **1i two-shape HMAC `preview_token` (DC-B1): one digest, no key, one choke point.** Every preview result (a draft's `preview_entry`, and every tool called without `confirmed`) returns `preview: sha256(canonical preview payload)` (draft: canonical draft state; at-once operation: canonical args + journal head seq); every confirm takes `preview`, recomputes, refuses `stale_preview` on mismatch. Computed in the result envelope, not per tool; no key file; required from the P4 sweep on (no "one release" grace).
- **1j jobs as lease holders (DC-C2): remove with `books.job`.** 3.9 as built lets a running request finish before a close.
- **1k audit split (DC-C3): accept the "current head" definition (a sentence), not a layered architecture.** Pure checks beside `Ledger::control_check`; `Store::verify`/`verify_blobs`/access-log chain in the store; the tool composes. One `audit.run {through, through_seq, workpaper_attachment, findings}` event.
## 2. The template: 8 fields, not 17
Keep: **Status**; **Depends on** (merge-first units and the owner go-ahead); **Feature and schema** (allocated name and projection schema number, or none); **Build** (each bullet ends `crate/path::symbol new|changed`; Core/Store/MCP/Viewer sub-bullets only where present); **Files** (owns / also touches); **Tests** (named, with file; proofs here too); **Done when** (numbered, checkable by the EM without the owner's books); **As built**. Drop: Runs in parallel with / Blocks (derivable; state once in the 8.0 table), Decisions in force (one reverse index in 10.4), Defects closed (fold into Done when), Docs (say once in 8.0), Risks (§9 only; a unit-specific risk is a sentence in Build), Open questions (a unit with one is not ready; they live in 10.3 and block Status). Keep the P4b reference design adjacent to the P4b slices; strike superseded sentences inline "→ D-n".
## 3. Do the zoom-out cuts remove anything D-19..D-46 requires?
- **3a P7 → scanner fix + decision: no conflict.** D-19's "human path (P2 `/h/`, P7 vault)" is superseded by D-28/D-29; D-31 needs only that the key travels in the backup (it does). Strike v2 residue in 3.4, 3.5, §6 (`keyring.json`, `preview.key`, `human.*` actions, `unwrap_only_in_human_handler`) "→ D-28/D-29".
- **3b P6 → extraction + dedup + annotations: keep one thing.** D-20: "P6 adds what makes it pleasant": document categories (notices, plans, history) and the viewer's Documents page grouped by category. Keep them (small) or record a deferral.
- **3c P9 split: one wording fix.** Top decision 4 says claude.ai on the web can reach the self-host machine, so P9b (claude.ai via D-7(b) external IdP) stays a planned unit after the P10 path, not "when wanted". P9c (replica viewer, invites) deferred by a decision row.
- **3d P8: partly conflicts with D-9.** "Counted full audit" must be computable, so the bar cannot be prose only. Minimal: one `policy.attestation_bar` setting (effort level, verifier count); attest tools record the self-reported `method`; "counted" evaluated against the bar in force at query time. Drop bar versioning, prospective semantics and the Lean lemma.
- **3e P11: "minisign only" conflicts with D-25/D-26 as recorded** (the two-key manifest with rotation and expiry is the owner's choice). Do not cut silently: one owner question (keep the manifest or minisign only), recorded either way. The P11a/P11b split and "the dock spawns the agent as an ordinary child" are safe; D-27 keeps the dock a unit.
- **3f Z-4, Z-9, Z-13, Z-17: no conflicts** (D-34 says bookspec is "its own public project": say it moves out when the harness is stable; the 56 examples keep running in `just check` meanwhile).
## 4. Missed at the architecture level
- **4a Major. Six P4b features where the owner decided one remaining upgrade.** `document_standing` is declared on the live books (2026-10-24). Answer 5 leaves exactly one more go-ahead, after S7. Fold S4-S7 into one feature, `documents_v2`, declared once after S7 (with 1f, `control_lines` disappears). State once: on the owner's books, S4-S6 writes carrying new fields or event types are refused with the upgrade remedy until the S7 upgrade; everything else works at once. If that wait is unacceptable, the EM asks the owner once for an upgrade after S4.
- **4b Major. Order.** With §0 and 1i, P4 is small and has no reason to precede S4-S7. Order: S4 → S6 (S5 and S7 beside), then P4 as one sweep adding `preview` to every confirm tool at the envelope, then P5.
- **4c Nit.** Settings nobody asked for in S4: `invoicing.paper`, `reminder_days`/`remind_days` as settings (constants until asked); q×u only as printed text, "refused unless q×u equals amount exactly at the given scale".
- **4d Nit.** "Filed in the same store call" vs D-46 (B6): D-46 is about the agent's context, not server-internal writes; the server stores the bytes it rendered through the store's own attach path (`Store::attach_receipt` takes bytes); the DRAFT preview PDF is a `HandedFile` like any export.
- **4e Nit.** The attention move is a 700-line refactor: `attention::find` reads a GUI-built `Books` (origin labels, `missing_receipt`, depreciation maps, W-9 status). Moving it over `PackageBooks` means generalising that builder from `Snapshot` to `&impl PackageBooks` (books.rs:689ff). Size the pre-step accordingly; still no new crate or trait.
## Recommendation (the shortest list that makes the document executable)
(1) Drop reversal-as-draft everywhere. (2) Rewrite P4 as a small sweep after P4b: draft states, `discard_draft`, the policy function moved into ledger-core and called by `Store::post_entry` and the GUI, one `preview` digest at the result envelope required by every confirm, defect 6's digest fields. (3) In P4b: one remaining feature (`documents_v2`) with one upgrade after S7 and the stated refusal window; the fee in S6 by the payout pattern with one kernel identity check; `line` indices on the existing payment events; handbook procedures for recovery, replacement-before-bounce (via 2300) and discounts on asset bills (refused with remedy); S7 cut to `write_off_invoice`; `adopt_control_line`, `move_balance`, `owed_on`, `Movement::Recovery`, `excess_to` and apportioned discounts each recorded as a deferral decision. (4) P5 without `books.job`, URL elicitation or jobs-as-leases; P6 as extraction + dedup + annotations + D-20's categories; P7 as the scanner fix + a decision closing vault v2; P8 composed from existing checks with one bar setting evaluated at query time and one `audit.run` event; P9 as a recipe + bearer unit now and a D-7(b) claude.ai unit later, remote viewer and invites recorded as not planned; P11 split, with one owner question on the D-25 manifest before any cut. (5) A pre-step unit, single owner: `server.rs` split by tool group and `attention::find` moved over `PackageBooks`. (6) The 8.0 status/dependency/shared-files table and the 8-field template; superseded text struck inline "→ D-n"; symbol names never line numbers; the owner's answers numbered A-1..A-14.