11 KiB
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 foramount" (ledger.rs:6466). Simpler: addline(anddiscount_line) indices to the existinginvoice.payment_received/bill.paidevents undersettlements; 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 thenreceive_payment; prior year, a plain entry Dr deposit / Cr "Bad debt recoveries" (revenue, tax categoryother_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_accountaccepts 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, behindsettlements. - 1f
adopt_control_linevariants (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'sunattachedterm exists for). Build it only ifbooks.control_checkon the live books lists a line neither remedy can take. Same forassign_role move_balance(B-23 already says a control role with a balance stays). S7 shrinks towrite_off_invoice+bad_debtsrole + B-41. - 1g
owed_onper-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-configintoledger-coreas one function over(&Ledger, &JournalEntry, &Settings); exemptions by origin use whatLedgeralready knows (closing entries, schedule postings, depreciation runs); (2)Store::post_entrycalls 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 existingmissing_receiptitem; 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'spreview_entry, and every tool called withoutconfirmed) returnspreview: sha256(canonical preview payload)(draft: canonical draft state; at-once operation: canonical args + journal head seq); every confirm takespreview, recomputes, refusesstale_previewon 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. Oneaudit.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_barsetting (effort level, verifier count); attest tools record the self-reportedmethod; "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 checkmeanwhile).
4. Missed at the architecture level
- 4a Major. Six P4b features where the owner decided one remaining upgrade.
document_standingis 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_linesdisappears). 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
previewto every confirm tool at the envelope, then P5. - 4c Nit. Settings nobody asked for in S4:
invoicing.paper,reminder_days/remind_daysas 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_receipttakes bytes); the DRAFT preview PDF is aHandedFilelike any export. - 4e Nit. The attention move is a 700-line refactor:
attention::findreads a GUI-builtBooks(origin labels,missing_receipt, depreciation maps, W-9 status). Moving it overPackageBooksmeans generalising that builder fromSnapshotto&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.