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

11 KiB
Raw Blame History

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.