docs: land the 2026-10-26 plan review and the team hand-off (plan/HANDOFF.md, served.md section 8)

This commit is contained in:
swaits committed 2026-10-26 15:20:00 -06:00
1 parent c1f9001cfe
commit 1bfce9157a
12 files changed
+1601 -945

No files matched your search

+80
View File
@@ -0,0 +1,80 @@
# Hand-off: the remaining wanderbooks work, for a team running in parallel
This file is the prompt to give the implementing team's harness (an engineering-manager agent that coordinates worker agents, each in its own git worktree). Paste the block under "The prompt" as the manager's instructions. Everything it refers to is in this repository. Written 2026-10-26.
## The prompt
You are the engineering manager (EM) of a team implementing the remaining work on `wanderbooks`, a Rust double-entry bookkeeping system. You coordinate worker engineers, each working in one git worktree on one unit of work. You do not design; the design is done. You assign units, enforce the bar, merge in the stated order, and ask the owner the stated questions at the stated moments. Your workers follow written instructions literally and do not invent missing design: when the plan does not say, they stop and ask you, and you look it up or ask the owner.
### Read first, in this order, before assigning anything
1. `AGENTS.md` (the repo's operating manual: the six non-negotiables, the verification protocol, the formal-methods bar).
2. `plan/served.md` section 0 ("How to read this document") and section 8.0 ("How the work is organized"): the status and dependency table (8.0.1), the dependency graph and merge order (8.0.2), the shared files, their owners and the allocated names (8.0.3), every unit's bar and the eight-field template (8.0.4), the recipes R-A to R-G (8.0.5), the journal-safety checklist (8.0.6), how to work in a worktree (8.0.7).
3. `plan/served.md` section 10 (the decisions, D-1 to D-54; the open questions Q-4 to Q-7; the decisions-by-unit index 10.4) and section 2 (the invariants I-1 to I-13).
4. `plan/STATUS.md` (where the live books stand) and `docs/architecture.md`.
5. Only when a unit is assigned: that unit's section in `plan/served.md` 8.2, and the parts of 8.3 (the P4b reference design) it points into.
### What is true today (2026-10-26)
- `main` is at the commit that landed P4b S3 or later. P0, P1, P2, P3, Projects and P4b S1, S2, S3 are built and live on the owner's books. Nothing after that is built.
- The plan's section 8 was rewritten on 2026-10-26 for this hand-off. Sections 1 to 7 are the older design and carry strike-throughs (`~~text~~ → D-n`) and "As built" notes where the build or a decision departed; where section 8 and an earlier section disagree, section 8 is right.
- Mutation testing is paused until unit P12 (decision D-33). Workers never run `cargo mutants`; each unit writes a "For P12" paragraph in `docs/testing.md` instead (recipe R-G).
- The owner's live books are never touched by anyone on this team. The only books a worker uses are test fixtures and throwaway books made by `wanderbooks setup` in a scratch root. A unit's new writes are refused on the owner's books until the owner's format upgrade; merging never waits for that.
### How to run things
- `mise install` once (Rust 1.99 with clippy and rustfmt, just, Kani, Lean, sccache, all pinned in `mise.toml`). Then `just` lists the recipes.
- `just check` is the gate for every PR: `cargo fmt --all --check`, `cargo clippy --workspace --all-targets -- -D warnings`, rustdoc with `-D warnings`, `cargo test --workspace`, the server-only build. It takes a while; during development, run the crate's own tests (`cargo test -p ledger-core`, `cargo test -p ledger-store`, `cargo test -p ledger-cli --test documents`) and run `just check` before opening the PR.
- `just prove` runs the Kani harnesses and the Lean proofs (about five minutes on a quiet machine; the slow harnesses are `just prove-slow`). Required for any unit that touches `crates/ledger-core/src/kernel.rs`, `crates/ledger-core/src/proofs.rs`, `verify/lean/`, or adds a `Ledger` command.
- Over-the-wire tests (`crates/ledger-cli/tests/*.rs`) start a real books server in a temp root through `crates/ledger-cli/tests/common/mod.rs`; see recipe R-C step 6.
### Assigning work
Use the table in 8.0.1 and the graph in 8.0.2. The order that is forced:
1. First, in parallel: **U0a** (split `server.rs` by tool group; single owner), **U0b** (the attention engine over `PackageBooks`; single owner), **U0c** (three missing `docs/testing.md` sections), **P4b S4.1** (core: terms, numbers, line accounts) and **P4b S4.2** (settings and party fields), **P13** steps 1 to 3 (the bookspec checker crate), **P7** (the TIN scanner's separators), **P9a** (the self-host recipe).
2. When U0a has merged: no other branch may add MCP handlers before it; after it, every unit adds handlers only in its own `crates/ledger-mcp/src/tools/<group>.rs`.
3. When S4.1 and S4.2 have merged: **S4.3** (store), then **S4.4** (MCP, needs U0a), then **S4.5** (PDF, viewer, handbook). **S6** may start once S4.1 has merged; **S5** once S4.2 has; **S7** once S4.3 has. **P6** may start once U0a and U0b have merged.
4. When S4 to S7 have all merged: **P4** (one engineer; its step 4 touches every tool that takes `confirmed`, so no other MCP branch is open while it merges).
5. Then **P5** (its step 1, the result envelope, is single-owner and merges when no other MCP branch is open), then **P8** (needs P6 and P4), then **P10** (a runbook; the owner runs the re-audit).
6. **P11a** any time (its signing steps need certificates and keys from the owner; Q-4 before its step 5), then **P11b**. **P12** last, over everything.
A worker's assignment is one message: the unit id, the exact section of `plan/served.md` to read (for example "8.2, the unit `P4b S4`, steps S4.1 only; read 8.3.3 and 8.3.4 for the design"), the worktree name (`wb-<unit>`), the branch name, and the reminder that the unit's "Done when" list is what you will check. Give the worker the template below.
### The worker's instructions (give these to every worker, verbatim)
> You are implementing one unit of `plan/served.md` section 8.2 in a git worktree. Read `AGENTS.md`, then `plan/served.md` section 0, section 8.0 (all of it: the template, the recipes R-A to R-G, the 8.0.6 checklist, 8.0.7), then your unit's section, then the parts of 8.3 and of sections 2 to 7 your unit's text points to. Do not read other units' sections to borrow from them; every name you may use (features, event types, setting keys, tools, roles, handbook sections, rule numbers, test files) is allocated in 8.0.3, and if your unit needs a name not listed there, stop and ask the EM.
>
> Follow your unit's Build steps in order. Each step compiles and passes `cargo test` for the crates it touches on its own; `just check` passes before every PR; `just prove` passes before any PR touching `kernel.rs`, `proofs.rs`, `verify/lean/` or a `Ledger` command. Every code bullet in your unit ends with where it lives (`crate/path/file.rs::symbol`): edit exactly there; for a "(new)" symbol, put it where the bullet says. Use the recipe named in the bullet (R-A for a journal feature, R-B for an event type, R-C for an MCP tool, R-D for a setting key, R-E for a handbook section, R-F for a Needs-attention item, R-G for the For-P12 paragraph); the recipe is the checklist of files you must touch, and tests fail on a partial state.
>
> Name every test exactly as your unit names it, in the file it names. Update the six docs the same PR (`docs/parity.md`, `docs/spec/rules.md`, `docs/mcp-interface.md`, `docs/testing.md`, `docs/verification.md`, `docs/architecture.md`) where your unit says so; a test fails if a tool is undocumented. If your unit changes a journal line's shape, do every item of 8.0.6 and say so in the PR.
>
> Never: run `cargo mutants`; edit a golden file without recomputing the figure by hand and explaining it in the commit; change a test your unit does not name (if one fails, you have broken something; fix the code or stop and ask); touch the owner's books or anything outside the repository; invent a feature name, event type, setting key, tool name, rule number or handbook id; change `plan/served.md` except to fill in your unit's "As built" block at the end; use a floating-point type anywhere money or a rate passes (AGENTS.md rule 1); let a posted entry change (rule 3).
>
> Commits: conventional (`feat(arap): terms and due dates (P4b S4.1)`), one logical change each, the message explaining the accounting reason, not just the code. A PR covers the steps your unit groups together, and its description lists: the steps done, every test added by name, the docs touched, the 8.0.6 checklist where it applies, and any question you had to answer yourself (with the two or three answers you considered and why you chose one). Rebase on `main` before the PR.
>
> If a Build step is impossible as written (a symbol it names does not exist, a type does not match, a test it names cannot be written as described), stop, write down exactly what you found, and ask the EM. Do not work around it silently. If you are stuck for more than an hour on a question the plan does not answer, write the question into the PR, pick the answer that keeps the books right under section 2's invariants, and flag it.
>
> Finished means: every item of your unit's "Done when" list is true and checkable without the owner's books; `just check` (and `just prove` where required) is green on your branch rebased on `main`; your "As built" block is written. Then report to the EM with the PR link and the list of Done-when items, each with how it was checked.
### Reviewing and merging (the EM)
- Review a PR against the unit's text and `AGENTS.md`, not against taste. Check: every named test exists and passes; the recipes' files were all touched (R-C: `types.rs`, the handler, `scopes.rs`, `docs/mcp-interface.md`, the over-the-wire test; R-A: the constant, the `_is_its_own_feature` test, the `ensure_feature` call sites, the `books_p2_served_cut_over_whole` entry; R-B: `EVENT_TYPES`, the two arms, the fuzz generator, `differs_from`); the 8.0.6 checklist where the journal's shape changed; no golden figure moved unless the unit says it may; no new dependency in `ledger-core` (AGENTS.md); no path, `just` or toml word in any tool text (`tool_text_lint`).
- Merge in the 8.0.2 order. The three single-owner moments (U0a; P4's sweep; P5's envelope) merge with no other MCP branch open. Schema numbers are taken at merge: the second unit to merge rebases and takes the next number (8.0.3).
- After each merge: move the unit's row in 8.0.1 to "landed (commit, date)"; make sure its "As built" block is filled; append a paragraph to `plan/STATUS.md`.
- Ask the owner only the questions in 10.3, at the moments stated (Q-5 when S4 lands; Q-4 before P11a's signing step; Q-6 before P9b; Q-7 when P11b starts), one at a time, in plain English, with the recommendation given there. Record the answer as a new D-n row in 10.1 and note which units it affects. Any other decision a worker needs that the plan does not make: look for it in section 10 first; if it is not there, ask the owner the same way and record it; never let a worker guess.
- The owner's format upgrade (D-39, D-52) is the owner's own command on his books after S7 and P4 have landed (8.3.14 item 8; 5.6's P4b row). It is not a team step.
### Stop conditions
Stop and report to the owner, instead of continuing, if: a unit's Build turns out to need a change to a landed unit's journal shape (never done without a new feature and a decision); a test outside the unit's list fails and the cause is not in the unit's diff; `just prove` fails (a failing proof is a finding, AGENTS.md "Formal methods" item 5: get the counterexample, do not weaken the assertion); the owner's books are involved in any way; a worker proposes a dependency on a crate not already in the workspace for `ledger-core`.
### Reporting
Weekly, or when asked: the 8.0.1 table as it stands (which units landed, with commits; which are in progress, with branch and engineer; which are blocked, on what), the open owner questions and their state, and anything a worker flagged in a PR that you could not resolve from the plan.
## Notes for the person handing this off
- The review that produced this plan is in `plan/review-2026-10-26/`; `consolidated_review_report.md` lists every finding and what was done with it. Nothing in the code changed on 2026-10-26.
- The sizes in 8.0.1 are rough, for an entry-level engineer new to the codebase; the recipes exist to make them hold.
- Four owner questions are open (Q-4 to Q-7, section 10.3). None blocks the first wave of units.
+27 -2
View File
@@ -1,6 +1,31 @@
# STATUS — 2026-10-22: "Served" P0 to P3 live, with Projects
# STATUS — 2026-10-26: "Served" P0 to P3 and P4b S1-S3 live; the plan reworked for a team
## Now: the plan is a hand-off (plan/served.md §8.0); P4b S4 next
**2026-10-26: the plan was reviewed and rewritten for a team of engineers
working in parallel worktrees.** A nine-stage Elephant-Goldfish review
(`plan/review-2026-10-26/`, consolidated in
`consolidated_review_report.md`) found `plan/served.md` sound as a design
record and not executable as a hand-off: no remaining unit could be built
from the text alone, P7 contradicted D-28/D-29, P4 was never reconciled
with the P4b slices built after it, every code pointer was stale, and
nothing stated status, dependencies, file ownership or done criteria.
Section 8 of the plan is new: 8.0 (the status and dependency table, the
merge order, the shared files and allocated names, the eight-field unit
template, the recipes R-A to R-G, the journal-safety checklist, how to
work in a worktree), 8.1 (what landed, with As-built blocks for P4b
S1-S3), 8.2 (every remaining unit in merge order: U0a, U0b, U0c, P4b S4
to S7, P4, P5, P6, P7, P8, P9a, P10, P11a, P11b, P12, P13), 8.3 (the
corrected P4b reference design; the owner's answers numbered A-1 to
A-14), 8.4 (what is not scheduled, and why). Decisions D-47 to D-54 and
open questions Q-4 to Q-7 were added; superseded sentences in sections 1
to 7 and 9 are struck through with the decision that superseded them.
The hand-off prompt for the implementing team is `plan/HANDOFF.md`.
Nothing in the code changed. **Next: U0a and U0b (the pre-steps), then
P4b S4 with S5 and S7 beside it (8.0.2).** The first owner questions in
line are Q-5 (one upgrade after S7, or an earlier one after S4) and Q-4
(the release manifest, before P11a's signing step).
## Now: Served (plan/served.md), P0 to P3 live; P4 next
The 2026-10-16 audit of the first company's books (62 verified findings,
20 engine defects) and the design discussion that followed produced
@@ -0,0 +1,106 @@
# Stage 6: Comprehension review of `plan/served.md` (DetailedRange 766-2058)
Reviewer: `egm-comprehension`, 2026-10-26, as the entry-level engineer handed ONE unit in a worktree. Symbols checked under crates/, docs/, verify/, bookspec/, fuzz/.
**Verdict:** no remaining unit (P4b S4..S7, P4, P5..P13) can be re-implemented from the text alone without guessing. The P4b slices come closest (P4b.3-P4b.11 carry real contracts); P5-P13 are scope lists, not specifications.
## A. Cross-cutting gaps (every unit)
- **A1 Blocker. Numbered references that resolve to nothing.** "defect 2" (P4 title) is never described; "defects 4, 10, 20" (P6) only by a phrase; "decision 1, 8, 9, 11, 12" (P4b.5/7/8/14/17) do not match the numbered owner answers 1-5 (they come from a question-tool session not in the repo); "the private change log" (909, 1486, 1623, 641, 969) and `goldens/p4-arap-before/`, `goldens/p4-arap-sN/` (1477, 1495) are outside the repo (the only in-repo thing with that name is a journal fixture under `crates/ledger-store/tests/fixtures/`).
- **A2 Blocker. "Done" is not defined per unit.** P4b S4-S7 have no Accept block; P4b.15 lists ~55 tests for seven slices without saying which slice lands which (`an_invoice_from_issue_to_paid` spans S4, S5 and S6). P5-P9 Accepts are mostly live-session or private-data checks.
- **A3 Major. Stale source anchors.** Every `S:NNNN`, `ledger.rs ~NNNN`, `types.rs:NNNN`, `docs/mcp-interface.md (24-95, …)` anchor; "schema v12" vs `SCHEMA_VERSION = 15`.
- **A4 Major. "for one release" (83, 442, 1294, 1933) is undefined**; no release versioning scheme is stated.
- **A5 Major. Where "Needs attention" items live is undefined** for P4, S5, P6, P7, P8: `ledger-view` does not exist and no phase's scope creates it.
- **A6 Major. Dependencies and parallelism are never stated as a graph.** Hidden: S5/S6/S7 "take preview_token once P4 lands"; S7 adopt/move hits P4's gate; P5's `next_steps::decorate` needs the attention engine; P8 cross-checks need P6; P10 needs P6, P8, P9; P11's updater needs `books.job` and `wanderbooks rollback` (listed in P2 scope at 859 but never built). Shared files every slice edits: `server.rs`, `types.rs`, `scopes.rs`, `format.rs` KNOWN_FEATURES, `schema.rs`, `docs/mcp-interface.md`, `docs/spec/rules.md`, `docs/testing.md`.
## B. P4b S4 `documents_v2`
- **B1 Blocker (1002 vs 1215/1631).** Answer 3 requires `invoicing.payment_methods` list with QR; P4b.4/11/17 specify a single `invoicing.payment_instructions` string and "that key only" exempt. `qrcode` 0.14.1 already in `ledger-import`.
- **B2 Blocker (1289-1298, 1236).** Preview-then-confirm changes today's `create_invoice`/`create_bill` contract (`CreateDocumentParams {party_id, date, due_date, lines}`). Unstated: behaviour on books without `documents_v2`; whether `due_date` becomes optional; `terms? | due_date?` with both or neither; fallback order (party default → `invoicing.default_terms`); the type of `terms` (string to parse or object).
- **B3 Major (1159-1163).** `Terms.text` has no composer; `EarlyDiscount.days < net` meaningless for NetEom/DueOnReceipt; `due_date -> Result` error cases; `0..=3650` vs `u16`; reuse of `validate_due_date` (arap.rs:599) not said.
- **B4 Major (1194-1198, 1492).** Zero-pad width; behaviour at 10000; `first_number` "after the highest legacy id" while legacy invoices have no number; prefix change after first issue; preview number vs confirm number.
- **B5 Major (1170).** `quantity × unit_price == amount exactly`: decimal scale unspecified (3 × 33.333).
- **B6 Major (1291, 1371-1373).** Filing a server-generated PDF "in the same store call" needs a new store path (D-46: all attachments arrive by upload). File name, MIME, uploader principal, and how the DRAFT preview PDF is returned (HandedFile? counted against the 64 cap?) unstated.
- **B7 Major (1362, 1218).** Seller snapshot wants "email or website"; no `company.email`/`company.website` setting exists.
- **B8 Major (1296-1297).** `create_bill`: `vendor_ref` required but legacy bills lack one; omission of `upload_id | attachment_id` refused or allowed; `received_on` has no stated use.
- **B9 Major (1290, 1311).** `bill_to?` type; `update_party` `default_terms` one value for both customer and vendor sides?
- **B10 Major (1331-1337, 1595).** "The handbook procedure": no section id; `crates/ledger-mcp/handbook/` has 21 sections and 4.3's list (407) names different ids. B-39/B-40 need writing; B-38 already exists (rules.md:2872) so "new rules B-38 to B-41" is partly stale.
- **B11 Major (1268).** Which crate gates `settings.set` of `invoicing.*` behind the feature (`setting_feature` is `pub(crate)` in ledger-store; the registry is in ledger-core); the exact list of writes that must still work without the feature is not enumerated.
- **B12 Nit.** arap `Snapshot` collides with `ledger_store::Snapshot`. **B13 Nit.** `customer` name resolution when a name matches two (precedent `inv6_ambiguous_vendor_reference_refused`).
- **B14.** No Accept; candidate tests exist by name only.
## C. P4b S5 sent tracking
- **C1 Blocker (1003 vs 1185-1186/1269).** Answer 4 requires who sent and the channel's reference; `Sent`/`invoice.sent` carry neither.
- **C2 Major (1325).** Defaults for `on`, `via`, `kind`; `to` when the party has no email; mark_sent on Void/Paid/legacy; whether bills get sends.
- **C3 Major (1330).** "never sent (after 2 days)": from issue date or today, which timezone, calendar days; `reminder_days` is in S4's settings list.
- **C4 Major (1342).** mailto body "about 1,800 characters" is not a contract; S3 already built the mailto link; S5's delta unclear.
- **C5.** No Accept.
## D. P4b S6 paying on terms
- **D1 Blocker (1003 vs 1597/1244/1300).** The processor fee (gross to A/R, fee to expense, net deposited) is assigned to S6 by STATUS but appears in no S6 line, posting row or argument; no fee role/account in the role table.
- **D2 Major (1300, 1304).** Top-level `amount` vs Σ applications; whether application `amount` is cash only or cash+discount; `paid_in_full_marking?` type and slice.
- **D3 Major (1323).** `books.bills_due` output has no schema; "amount needed by each date" undefined.
- **D4 Major (1352-1353).** Aging CSV columns, tie-out line format, README text, xlsx/pdf templates; `detail?` and `control{}` on the aging tools have no slice.
- **D5 Major (1204-1210).** "Expense (credit balance)" has no account-type variant; roles "re-checked against the live chart" is the owner's private books.
- **D6 Major (1259).** Is `discount_reason` journaled (feature row names only `discount`)?
- **D7 Nit.** Bill flags `due_soon`/`discount_open`: S5 or S6?
- **D8.** No Accept.
## E. P4b S7 bad debts and legacy lines
- **E1 Blocker (1326).** `write_off_invoice` "needs evidence attached in the same call" but has no `upload_id`/`attachment_id` argument.
- **E2 Blocker (1327, 1272).** `adopt_control_line`: how the Invoice/Bill record is built (date, lines, customer/vendor creation), what `record_id` is, numbering, replay determinism.
- **E3 Blocker (1312, 1251, 1272).** `assign_role move_balance`: entry dates, description, party tag, evidence under P4's gate, unattached legacy lines, zero balance, atomicity.
- **E4 Major.** Cash-basis refusal: error code/guard name. **E5 Major (1249).** Recovery reversal vs P4's reversal-as-draft. **E6 Major (1208).** On-demand role creation when 6990 is taken. **E7 Nit.** B-41 and Lean `bounds_preserved` are names only.
- **E8.** No Accept.
## F. P4 drafts, reversal as draft, evidence gate, digest
- **F1 Blocker (971).** "defect 2" undefined; exemptions named by stale line numbers; the live handbook (`drafts-and-reversals.md:4`) says reversals need no receipt while I-5 says they are not exempt.
- **F2 Blocker (978, 66, 83, 512).** `preview_token`: "canonical draft state" undefined; "all confirm tools" not enumerated; 83 vs 512 contradict on whether `confirmed` without a token is accepted; `preview_entry`'s output unstated.
- **F3 Blocker (974, 1109, 1316, 1228).** Does `void_document` become a draft after P4? Draft-time vs post-time for `check_reversal`/`check_item_bounds`.
- **F4 Major (974).** `ledger_core::evidence::requirement` does not exist (gate is `ledger_config::ReceiptPolicy`); signature and return unstated; "exemption is sourced" names no source.
- **F5 Major (513, 574).** How `link_original` is journaled; what posting a lineless reversal draft journals; replay rebuilds lines from which original state.
- **F6 Major (976-977).** `list_drafts` shape; `as_of_status` on which reports; `strict` semantics; `posted_seq`/`entry_meta` do not exist; "zero-net touched accounts", "reconciliation_tracked", "never_reconciled" undefined.
- **F7 Major (981-985).** Lean draft-state notion absent; `Step.discard` obligations; `tests/model.rs` extension unstated.
- **F8 Major.** Attention location (A5). **F9 Major (993).** "0 unexplained drafts" is private. **F10 Nit.** "gains the new ops" lists none.
## G. P5 agent surface
- **G1 Blocker.** `ledger-view` never created by any scope while `next_steps::decorate` and `books.start` v2 need it.
- **G2 Blocker (1643, 411-423).** Prompt texts, argument validation and outputs unspecified; "golden render per prompt" impossible.
- **G3 Major (1644, 444-460).** Guard code list, per-tool next-step rules, merging with existing `warnings`; `books.start` already returns `next_steps`.
- **G4 Major.** `books.job` retention, id, polling, cancellation, which tools; `journal_history` output and `subject` grammar.
- **G5 Major (1646).** Source-only revision: no event name or feature. **G6 Major (1647).** URL-mode elicitation has no local use (D-28). **G7 Major (1649).** Agent-eval harness mechanics; depends on P4. **G8 Major.** Accept on a nonexistent fixture and manual sessions. **G9 Nit.** Which eight descriptions exceed 700 characters.
## H. P6 evidence pipeline
- **H1 Blocker.** Sandboxed `ledger-extract` contradicts D-32. **H2 Blocker (1682).** Migration tools' inputs are private scripts and files; formats unspecified.
- **H3 Major.** `inline_base64_requires_sha` contradicts D-46; `get_receipt` views `text`, `page_image` outputs unstated; `books.download_link` ambiguous under D-46.
- **H4 Major.** `attach_email`: no `eml` upload purpose; decision 5 defers email-in; Typst template unstated.
- **H5 Major.** `text_extracted` fields; backfill trigger; receipt numbering field; FIFO `reimbursed` inputs undefined; `annotate_attachment` validation.
- **H6 Major.** Consolidation event names absent from 5.2. **H7 Major.** Company record category list. **H8 Major.** Accepts are private data; hayro already present, not mentioned.
## I. P7
- **I1 Blocker.** Contradicts D-28, D-29, 3.6 as-built; which parts survive is undecided. **I2 Blocker.** Keyslot formats, `rekey`, HPKE crate; 4.1 still names `request_tin_entry`. **I3 Major.** Accept against private goldens. **I4 Major (644 vs D-31).** "No vault key material on disk" is false for backups.
## J. P8
- **J1 Blocker (1727-1734).** Each check is a noun phrase; no predicates, finding codes, workpaper schema or Typst template; "2100 aging" is a company-specific code in a generic engine.
- **J2 Blocker (1738-1742).** Attest tools have no argument lists; `policy.attestation_bar` has no value schema; which P6 fields the cross-check reads.
- **J3 Major.** `workpaper` category, `books.workpaper`, `books.attestations` not in scope. **J4 Major.** The refuse-without-audit override parameter is unnamed. **J5 Major.** "access-log tooling" in the title, none in scope. **J6 Nit.** Lean lemma has no statement.
## K. P9
- **K1 Blocker.** D-7 undecided, determines most of the code. **K2 Blocker.** Archive unit, seccomp/Landlock, `fetch` do not exist (D-31, D-32). **K3 Major.** `tunnel` command or doc; MCP App widget unspecified; `/h/inbox` built here or not. **K4 Major.** Replica traits, headers, events unnamed; `invite` sends mail though the books never send. **K5 Major.** No two-user CI job exists.
## L. P10
- **L1 Blocker.** Entirely on private books by live agents; depends on P6, P8, P9; "22 defect reproductions" stale (23 and 24 exist). **L2 Blocker.** "No TINs through the agent" contradicts D-29. **L3 Major.** `fetch`/`verify-archive` never existed. **L4 Major.** "100K tokens" has no measurement method.
## M. P11
- **M1 Blocker.** Dock built on the helper and confinement D-32 removed. **M2 Blocker.** Signing identities, repository keys, hardware token, GitHub Releases: outside the repo and the engineer's authority; the unit begins with research. **M3 Major.** `rollback`, `update.lock`, `update`, manifest format, staged layout unstated. **M4 Major.** Books-home file contents not given. **M5 Major.** Tests need clean VMs; no CI definition.
## N. P12
- **N1 Major.** Hardware external. **N2 Major.** "set time" never set; location of documented maxima. **N3 Nit.** `--jobserver-tasks` unverified. **N4 Nit.** §6 promises recipes for crates that don't exist.
## O. P13
- **O1 Blocker (1886).** The in-repo seed (`bookspec/`, 56 examples, checked by `cargo test`) is never mentioned; an engineer would start from zero. **O2 Blocker (1873).** `Engine` trait has no signature; TOML schema is prose. **O3 Major.** Rule identifier scheme; mapping from B-1..B-38; no done-line for a first edition; Lean consistency claim has no formalization hint. **O4 Major.** Claims list and the GnuCash run undefined.
## P. Contradictions
P1 reversal-as-draft vs `void_document` posts at once. P2 `payment_instructions` string vs `payment_methods` list. P3 `Sent` without sender/reference vs answer 4. P4 fee in STATUS/S6 vs absent from S6. P5 PDF stamps: three different lists; `invoice_pdf(&InvoiceDoc, &DocMeta)` vs built `invoice_pdf(&InvoiceDoc)`. P6 "slice S1 next" vs S1-S3 landed. P7 P7's scope vs D-28/D-29/3.6. P8 sandbox/archive unit/helper vs D-31/D-32. P9 `inline_base64_requires_sha`, `download_link` vs D-46. P10 P10 step 6 vs D-29. P11 `verify-archive`/`fetch`/`heads.jsonl` vs D-31. P12 `confirmed` without token accepted (83) vs required (512). P13 schema v12 vs 15. P14 "22 defects" vs 24. P15 P8 title vs scope. P16 4.3 handbook ids vs 21 files. P17 drop page: P2 scope vs 4.7 as-built vs P9 Accept. P18 "new B-38" vs existing. P19 `ledger-view` in P5 per P1 as-built vs absent from P5 scope. P20 `goldens/p4-arap-before/` vs private goldens vs same-named fixture.
## Q. Symbol verification
**Exists:** arap.rs (DocRef, Movement, DocTag, DocState, DocStatus, ControlCheck, aging_bucket, document_parts, validate_due_date, derive_status); `Ledger::stands_on`, `in_effect`, `check_reversal`, `check_item_bounds` (private), `control_check`, `void_document`, `invoice_state`/`bill_state`; `UntaggedControlLine`, `PaymentBeforeDocument`; KNOWN_FEATURES with `document_standing`, `document_line_projects`; `setting_feature` (pub(crate)), `ensure_feature`; `CreateDocumentParams`, `PaymentParams {instrument?, reference?}`; `invoice_pdf(&InvoiceDoc)`, `InvoiceStamp` (5 variants), `invoice_document`, `PackageBooks`; `Subject::Invoice/Bill`; `has_full_tin`/`redact_full_tins`; `company.duns` exemption, `company.name`, `company.address`, `books.accounting_basis`; `qrcode`, `hayro`; Lean `control_agrees`, `control_agrees_through`, `Step.document_unattached`, `cashflow_sections_sum_to_cash_change`, `draftsOpen`; `tests/model.rs`; fuzz targets; `local_server.rs`, `tests/common/mod.rs`, `projects_parity.rs`; `receipt_policy.rs`; `tools/{access,backup,files,instruments,kept,projects}.rs`; `books.start` with `next_steps`; docs/testing.md "For P12" sections; scheduled.yml; `bookspec/` seed; docs/oracle.md; rules.md B-38.
**Missing:** `Terms`, `TermsKind`, `EarlyDiscount`, `Sent`, `SentVia`, `SendKind`, arap `Snapshot`, `issued_pdf`, `vendor_ref`, `customer_ref`, `duplicate_reason`, `default_terms`, `check_document_line_account`, every `invoicing.*`/`bills.*` key, `company.email`/`company.website`; features `documents_v2`/`invoice_sent`/`settlements`/`write_offs`/`control_lines`; events `invoice.sent`/`invoice.written_off`/`control_line.adopted`/`role.balance_moved`; tools `mark_sent`/`bills_due`/`write_off_invoice`/`adopt_control_line`; `ledger_core::evidence`; `preview_token`/`preview_entry`/`list_drafts`/`set_draft_state`/`discard_draft`/`inherit_evidence`/`as_of_status`/`posted_seq`/`entry_meta`; `ledger-view`, `ledger-extract`, `lopdf`, `mail-parser`; `text_extracted`/`find_attachment` tool/`consolidate_duplicates`/`annotate_attachment`/`compare_reference`/`record_inventory`/`check_inventory`/`attach_email`; `journal_history`/`books.job`/`guards` envelope; `audit.rs`/`attest_*`/`workpaper`/`attestation_bar`; hpke/`keyring.json`/`request_tin_entry`/`set_w9_facts`/`/h/` routes; `fetch`/`tunnel`/`invite`/`verify-archive`/`rollback`/`update`/`update.lock`; replica routes; `scripts/*`; `goldens/*`; rules B-39/B-40/B-41; a handbook section for invoices.
@@ -0,0 +1,57 @@
# Consolidated review of `plan/served.md` (P4b S4 through P13), 2026-10-26
Status: resolved (the fixes below were applied to `plan/served.md` on 2026-10-26; see "What changed").
Nine-stage Elephant-Goldfish review: Stage 5 zoom-out (two reviewers), Stage 6 comprehension, Stage 7 design/structural/style, Stage 8 intermediate zoom-out on the proposed mitigations, Stage 9 implementation readiness and codebase details. Stage reports in this folder. Reviewer id prefixes: Z (zoom-out), C (comprehension; letter.number), DC (design), ST (structural), SY (style), IZ (intermediate zoom-out), IR (readiness), CD (codebase details).
## Verdict
The document was sound as a design record but not executable as a hand-off: no remaining unit could be built from the text alone (C, all units), two phases contradicted the owner's recorded decisions (P7 vs D-28/D-29; P6/P9/P11 vs D-31/D-32/D-46), P4 was never reconciled with the P4b slices built after it, every code pointer was stale, and nothing stated status, dependencies, file ownership or done criteria. The accounting design of P4b is right in the code and wrong in one sentence of the plan (DC-A1). The remaining design carried speculative scope the owner never asked for (IZ, Z).
## Action items, by priority, with resolution
### 🔴 Blockers
| # | Action | Findings | Resolution |
|---|---|---|---|
| B1 | Add a status / dependency / shared-files / merge-order section at the head of §8, and one template for every remaining unit | Z-1, ST-F1, ST-F3, ST-F7, C-A2, C-A6, IR-CC-1, IR-CC-3, IZ-2 | PROPOSE FIX: new §8.0 (status table, dependency graph and merge order, shared files and owners, every unit's bar, recipes R-A..R-G, the journal-safety checklist); 8-field template (IZ-2's cut of ST's 17) |
| B2 | P7 rewritten: the vault stays v1; the scanner defect is the only code; vault v2 and TIN pages closed by decision | Z-2, ST-F2, C-I1..I4, SY (P7), IZ-3a | PROPOSE FIX: D-48; strike-throughs in 2.1, 3.4, 3.5, 4.1, 4.9, 5.2, 5.6, §6, 7.6, I-7, D-16, D-19 |
| B3 | Drop "reversal as a draft"; P4 becomes a small sweep after P4b; `void_document` stays immediate | IZ-0, DC-A2, DC-A3, DC-B3, ST-F4, Z-3, C-F1..F3, C-E5 | PROPOSE FIX: D-47; I-4 and I-5 reworded; P4 rewritten; 4.9/5.2 rows struck |
| B4 | Correct `outstanding(d, D)` to the date-sum (the shipped code and rules.md B-38 item 2) | DC-A1 | PROPOSE FIX in P4b.3 and P4b.13 |
| B5 | Number the owner's answers A-1..A-14 and fix every "decision N"; delete the override sentence; reconcile the four known conflicts (payment methods list; exempt keys; `Sent` with sender and reference; the fee) | SY-B1, SY-B2, ST-F13, C-B1, C-C1, C-D1, Z-14 | PROPOSE FIX |
| B6 | One remaining P4b feature (`documents_v2`) for S4-S7, P4's `drafts_v2` in the same upgrade; the refusal window stated once | IZ-4a, ST-F5, C-B11, SY-M16 | PROPOSE FIX; Q-5 asks the owner whether to upgrade earlier |
| B7 | S4: the issued PDF is filed as `receipt.attached` on the entry plus `attachment.linked` to `Subject::Invoice`; no `issued_pdf` field; render before journaling | CD-A6, C-B6, IZ-4d | PROPOSE FIX |
| B8 | P6: no sandboxed `ledger-extract`, no lopdf, no migration tools, no `attach_email`; extraction in `ledger-import` behind both callers; keep D-20's categories | Z-5, Z-6, DC-C4, C-H1, C-H2, CD-E1, CD-E3, IZ-3b | PROPOSE FIX; CD-E3 corrects earlier stages: the only PDF text reader is a test helper, so P6 promotes it |
| B9 | P9 split (P9a recipe + bearer now; P9b claude.ai via external IdP later; P9c not planned); P10 restated against what exists; P11 split and the helper references removed | Z-8, Z-10, Z-12, ST-F8, C-K1..K5, C-L1..L4, C-M1..M5, SY-B5, SY-B6, IZ-3c, IZ-3e, CD-G1..G5 | PROPOSE FIX; D-51 (P9c), Q-4 (P11 manifest), Q-6 (D-7) |
| B10 | P5: prompts capability does not exist yet; the envelope is a single-owner step; no `books.job`; `books.start` v2 extends the built structs | CD-D1..D3, IR-P5-1, Z-9, C-G1..G8 | PROPOSE FIX |
| B11 | P8: compose from existing checks; a `Finding` type; one `policy.attestation_bar` setting; one `audit.run` event; `workpaper` category; a check table | Z-7, DC-C3, C-J1..J5, IZ-3d, CD-F1..F3, IR-P8-1 | PROPOSE FIX |
| B12 | P13 starts from the in-repo seed and loader; `Engine` trait signature; in-tree until the harness is stable | Z-17, C-O1..O4, CD-I1, CD-I2, IR-P13-1 | PROPOSE FIX |
| B13 | Pre-step units with a single owner: `server.rs` split by tool group; attention engine over `PackageBooks` (no `ledger-view`); `docs/testing.md` sections for S1-B, S2, S3 | Z-4, Z-13, DC-C1, C-A5, IR-CC-2, IZ-4e | PROPOSE FIX |
### 🟡 Major
| # | Action | Findings | Resolution |
|---|---|---|---|
| M1 | S6: the fee by the payout pattern with one kernel identity check (`kernel::settle`) and Kani; line indices on the existing payment events; `kernel::discount_max`; bridge properties | DC-A5, DC-A4, DC-A13, IR-S6-3, IZ-1a, IZ-1e | PROPOSE FIX (CONTEST DC-A4's new event type and DC-A5's `payout_plan` refactor, per IZ) |
| M2 | S7 cut to `write_off_invoice` + `bad_debts` + B-41 + Lean `Step.writeOff`; `adopt_control_line`, `move_balance`, `owed_on`, `Movement::Recovery`, `excess_to`, apportioned discounts recorded as deferrals with handbook procedures | DC-A6..A9, DC-A11, C-E1..E3, IZ-1b..1g | PROPOSE FIX: D-49 (CONTEST the mechanisms; adopt the procedures) |
| M3 | Evidence gate: policy evaluation moves to `ledger-core`, `Store::post_entry` calls it, the GUI's string heuristic goes; at-once tools stay as today | DC-A10, DC-B2, C-F4, CD-C3, IZ-1h | PROPOSE FIX |
| M4 | Preview check: one `preview` digest at the result envelope, required by every confirm tool; no HMAC key | DC-B1, DC-A14, Z-19, C-F2, IZ-1i | PROPOSE FIX: D-50 amends I-6 |
| M5 | Strike-throughs with "→ D-n" for every superseded sentence in §§2-7, 9; one "How to read this document" section; §10.3 closed; new decision rows; 10.4 decisions by unit | Z-15, ST-F6, ST-F8, ST-F9, ST-F10, ST-F12, SY-M5 | PROPOSE FIX |
| M6 | Symbol names, never line numbers; stale counts and references fixed (defects 23, 24; schema 15; server.rs 12,566 lines; "22 reproductions") | Z-11, C-A3, SY-M6, ST-F14 | PROPOSE FIX |
| M7 | S4 contract gaps: `due_date` fallback order, `terms` type, numbering width and `first_number` default, q×u rule, `vendor_ref` on legacy bills, `bill_to` type, `default_terms` per side, handbook id, `Snapshot` → `PartySnapshot`, `company.email`/`company.website`, `PartyField` replay fix | C-B2..B13, SY-B3, CD-A3..A9, CD-A11 | PROPOSE FIX |
| M8 | S5 defaults and predicates; `Sent.by`, `Sent.reference` | C-C1..C5, IR-S5-2 | PROPOSE FIX |
| M9 | §8 preamble per D-33; per-unit "For P12" obligation | ST-F11, Z-20, C-N1..N4, CD-H1 | PROPOSE FIX |
| M10 | Style: "preview" vs "draft", "document" defined, slices qualified, hedges resolved, run-on lists split, `the the first company` fixed, `<Company>` placeholder | SY-M1..M22, SY-B4 | PROPOSE FIX within §8; the rest of the file keeps its prose (CONTEST the full rewrite of §§1-7 as out of scope) |
### 🟢 Nits
Tool-name formatting, `remind_days` naming, unit forms, `a | b` notation defined, heading 1573's procedure moved under the heading, verbless fragments, `DECISION` vs `CHOICE` mapping (SY-N1..N15, SY-M21): applied where §8 was rewritten; not chased through §§1-7.
## Contested (not applied)
- DC-A4 `settlement.posted` event; DC-A5 `payout_plan` refactor; DC-A8 adopt variants; DC-A9 `owed_on`; DC-A11 `Movement::Recovery`; DC-A6 `excess_to`; DC-A7 apportioned discounts; DC-B1 two-shape HMAC; DC-C2 jobs as leases: each replaced by the simpler resolution in the Stage 8 report (IZ-1a..1k) and recorded as a deferral (D-49) or a procedure.
- ST's 17-field template: 8 fields (IZ-2).
- Z-10's "minisign only": conflicts with D-25/D-26 as recorded; asked as Q-4 instead.
- SY-M8 moving P4b.1 out of the plan: kept (the handbook draws on it); the derived rules are listed beside it.
- IR's P4 step 3 (reversal as draft): superseded by IZ-0.
## Traceability
Every finding id above maps to one action item; findings not listed (SY nits in §§1-7, ST-F15/F16 formatting) were judged below the bar for this pass and are recorded here for a later editing pass.
## What changed in `plan/served.md`
See the top of the file ("0. How to read this document", "What changed on 2026-10-26") and §8.0.
+22
View File
@@ -0,0 +1,22 @@
# Review context (written 2026-10-26 by the reviewing session; facts, not opinions)
## Where the repository stands
- Branch `main`, head `c1f9001c` ("feat: the invoice PDF, with stamps and each payment (P4b S3)"). Working tree clean.
- `plan/served.md` is the plan under review (2058 lines). Its phases are P0..P13, with P4b inserted after P4.
- Built and live on the owner's company books: P0, P1, P2, P3, Projects, D-44 (stdio server removed), D-46 (files only by upload/download handles), P4b slices S1, S2, S3. See `plan/STATUS.md` lines 1-357 (current status) for what each one did.
- NOT built: P4 (drafts, reversal as draft, evidence gate, digest), P4b S4..S7, P5, P6, P7, P8, P9, P10, P11, P12, P13.
- The order the plan lists P4 before P4b, but P4b was started first (D-45, urgent defect). P4b.16 says "slice S1 next" although S1-S3 have landed.
- `bookspec/` already exists in this repo as a seed (56 example TOML files + README), checked by `cargo test`. P13 says bookspec gets its own repository later.
- Mutation testing is paused until P12 (D-33). Each phase lists what P12 must mutate in `docs/testing.md`.
- Crates (lines of Rust): ledger-core 74.6k, ledger-gui 43.9k, ledger-store 42.3k, ledger-cli 34.6k, ledger-mcp 28.3k, ledger-import 11.5k, ledger-host 8.7k, ledger-package 6.7k, ledger-config 3.4k, ledger-vault 0.7k. Lean proofs in `verify/lean/`. Kani harnesses in `crates/ledger-core/src/proofs.rs`.
- 118 MCP tools, documented in `docs/mcp-interface.md`; a test fails if a tool is undocumented.
## What the plan will be used for next (the purpose the review judges against)
The owner will hand the remaining work (P4b S4 through P13) to a team of ENTRY-LEVEL engineers working in parallel git worktrees, coordinated by an engineering manager who merges their branches. The engineers are expected to follow instructions literally and will not infer missing design. The plan must therefore:
1. say for each remaining unit of work exactly what to build, where (crate, module, file), what tests prove it, and what "done" means;
2. say which units can run in parallel and which must wait for another (dependencies, shared files, format features that need the owner's go-ahead);
3. not rely on knowledge that lives only in the previous engineer's head or in private files outside the repo;
4. be consistent with what has already been built (no scope text that was superseded by D-32, D-44, D-46, D-31 and still reads as if it were to be built).
## Repo rules the plan must respect (from AGENTS.md)
Money is exact Decimal; every entry balances; posted is forever; reports are pure functions of the ledger; clean room; money arithmetic in kernel.rs with Kani proofs; laws as Lean theorems; `just check` and `just prove` green before every commit; `docs/parity.md`, `docs/spec/rules.md`, `docs/mcp-interface.md`, `docs/testing.md`, `docs/verification.md` updated in the same commit; conventional commits.
+88
View File
@@ -0,0 +1,88 @@
# Stage 7: Critic review of `plan/served.md` (design, structural, style)
Reviewers: `egm-design` (feature-spec standard; scope P4b.3-13, P4, P5, P6, P8 against I-1..I-13 and D-28..D-46), `egm-structural` (whole document, emphasis §8-10), `egm-style` (§8 P4-P13, §9). 2026-10-26. Prefixes: DC = design, ST = structural, SY = style. Elephant verification notes in [brackets].
## Blockers
### DC-A1. `outstanding(d, D)` is defined over "entries that stand on D"; that double-counts re-reversals and contradicts the shipped code
P4b.3 1106-1113; P4b.13 INVARIANTS 1-3; Lean `control_agrees`. Origin +100, reversal −100, re-reversal +100: summing standing entries gives +200, the GL gives +100. [Verified: `Ledger::doc_state` (ledger.rs:5595) sums every tagged line dated ≤ as_of regardless of standing; `stands_on` is used only for the Void flag. The code is right; the plan text is wrong.] Fix: `outstanding(d, D) = Σ amount over posted lines tagged d with entry.date ≤ D`; `stands_on` only for Void; state the Lean theorem over the date-sum.
### DC-A2. Bounds and reversal checks run at creation; under P4 the reversal posts later, so a void can post under a payment
P4b.3 1130-1135; P4 974. `check_item_bounds` and `check_document_reversal` run in `check_reversal` when the reversal is created. If P4 makes a reversal a draft, a payment recorded between draft and post leaves outstanding −100. Same hole for schedule/depreciation checks in `Store::check_reverse_entry` and the un-posting side effects in `reverse_entry_steps`. Fix: posting an entry with `reversal_of` re-runs the whole reversal check at post time; a reversal draft is immutable (`add_line`, `set_line_class`, `set_entry_party` refused); Lean `Step.postReversal` separate from `Step.post`.
### DC-A3. The built immediate void/bounce is not reconciled with "corrections are reversal drafts that need evidence"
I-4, I-5, P4 971-979 vs P4b.6 1246-1247, P4b.7 1275, P4b.8 1316, owner answer 1. Either `void_document` bypasses the gate (contradicts I-5) or voids become two-step (contradicts answer 1). Fix: decide in writing: `void_document {confirmed}` creates and posts the reversal in one store call when the evidence requirement is satisfiable in that call (`link_original`: the issued PDF is the original's evidence; a bounce needs the bank's returned-item notice by `upload_id`); otherwise it returns a draft awaiting evidence. The draft step copies `DocTag`s and sets `reversal_of` before any line exists.
### ST-F1. No consistent per-unit shape; status, dependencies, ownership, done criteria absent for every remaining unit
§8 766-1889. Three shapes coexist. Only one "Status" line exists (997) and it is stale. Fix: the template below for P4, P4b S4-S7, P5-P13; a status/dependency table at the head of §8.
### ST-F2 / SY (also Z-2). P7 contradicts D-28 and D-29 and carries no marker
P7 1697-1722; also 2.1 row 84, I-7 (67), §6 line 671, top decision 5 bullet (19), D-16. Fix: rewrite P7 under the template; the `/h/` TIN pages move to P9 (remote only); strike "Removed from the MCP"; mark I-7, 2.1, 671, D-16 "changed by D-28/D-29".
### ST-F3. No file-ownership map or merge order for files every unit edits
`scopes.rs`, `server.rs`/`tools/*.rs`, `format.rs` KNOWN_FEATURES, `schema.rs`, `tests/format.rs`, `Ledger.lean` `Step`, `proofs.rs`, `settings.rs`, `handbook/*.md`, the six `docs/*.md`. Schema numbers cannot be allocated independently. Fix: "8.0.3 Shared files and merge order": owner, append-only reserved blocks per unit, feature names and schema numbers allocated up front.
### ST-F4. P4 was written before P4b and never revised against S1-S3
P4 971-993 vs P4b.3 1104-1109, P4b.8 1285-1307. P4 names no feature flag for four new event types; says nothing about tag copying on reversal drafts, draft-time vs post-time bounds, `stands_on` vs drafts, `void_document` on a draft. Fix: rewrite P4 with "Depends on P4b S1-S3 (landed)" and those integration points; name its feature.
### ST-F5. P4b status and slice list stale and self-contradictory on upgrades
997; 1004 "two upgrades"; 1265-1272 six features; 1281; 1629 "six go-aheads"; 1477. If upgrades are batched after S1 and S7, S4-S6 writes refuse until S7 lands; never said. Fix: per-slice Status; "Declared on the owner's books" column in P4b.7; state the batching rule and its consequence once; each slice its own H4.
### SY-B1 / ST-F13. "decision N" (N > 5) references resolve to nothing
1236, 1281, 1297, 1301, 1493, 1629. Answer 5 is an unnumbered run-on of nine items; any enumeration a reader reconstructs is off by one against the references. Fix: number the owner's answers A-1..A-14 (one per line) and rewrite each reference; reserve "decision" for D-n.
### SY-B2. The override sentence (997) tells readers to resolve contradictions themselves
Known conflicts it covers: `payment_methods` list vs `payment_instructions` string (1002 vs 1215); plural exempt keys vs "that key only" (1002 vs 1215/1631); `Sent` without sender/reference vs answer 4 (1003 vs 1185, 1325); fee vs no fee (1003 vs 1300/1304). Fix: delete the sentence; edit the later text to match the answers.
### SY-B3. `Snapshot` names two Rust types
1165 (party snapshot) vs the books `Snapshot` (1128, 1418, 1549, 1762). Fix: `PartySnapshot`.
### SY-B4. Broken find-and-replace: "the the first company", "`~/Documents/the first company-Books`" (1819, load-bearing in P11), 1778, 1646, 1657, 1694, 1721 (and 85, 405, 805, 841, 969). Fix: one placeholder, `<Company>` in paths, "the owner's company" in prose.
### SY-B5 / SY-B6. P9 1759 and P11 1801 each carry six or more instructions and an exception in one breath. Fix: tables (unit | IPAddressDeny | IPAddressAllow | …; format | built by | installs as | updated by | signed by) and one instruction per bullet.
## Major
### Design (DC)
- **A4.** The ordinal rule for multi-document payments breaks once discounts add a second control line per document; rules.md B-38 item 1 states a different rule ("first untagged control line for the amount"); "needs no feature" rests on an older binary's behaviour. Fix: under `settlements`, one event per entry with explicit line references `settlement.posted {entry_id, applications: [{line, doc, movement, amount}]}`; positional inference only for legacy single-document events; multi-document payments behind the same feature.
- **A5.** The processor fee (answer 4) has no posting row, argument, feature, role, kernel function or party rule; replay's line-by-line check would fail to open books with a fee line unless behind a feature; the entry-level customer tag misattributes the fee; `payouts.rs:178` already computes `gross − fees` outside the kernel. Fix: in `settlements`: `receive_payment {…, fee?: {amount, account?, vendor?}}`; posting row; `kernel::settle(applications, discounts, fee)` with Kani (and `payout_plan` calls it); role `payment_processing_fees`; fee line carries the processor's vendor tag or is a second entry in the same store call.
- **A6.** "0 ≤ outstanding at every date" refuses a real temporary overpayment (replacement payment received before a bounce is known); the only escape is mis-dating. Fix: `receive_payment` takes `excess_to: <liability holding no role>` (2300) in S6, or the plan says what the agent does.
- **A7.** A purchase discount on a bill with an asset line goes to expense, overstating the asset's cost and tax basis. Fix: apportion the discount by `kernel::apportion` across line accounts (asset lines reduce cost), `purchase_discounts` for the expense portion only; or refuse discounts on bills with asset/prepaid lines in S6 and name the remedy.
- **A8.** `adopt_control_line` can only create origins, so a legacy hand payment line becomes a phantom credit-balance document; an adopted document's `total` is undefined (`doc_header` uses `lines.total()`). Fix: `as: invoice | bill | payment_of: <doc> | discount_of | write_off_of`; an adopted origin gets one synthesized line (`account: None`).
- **A9.** The identity is per account but `outstanding` is per document; after `move_balance` a document's lines straddle two accounts and the Lean theorem is ill-defined. Fix: `owed_on(A, d, D)` per account; `GL(A, D) = Σ_d owed_on(A, d, D) + unattached(A, D)`; `outstanding(d, D) = Σ_A owed_on`.
- **A10 / B2.** The "single evidence gate in `Store::post_entry` (every path)" covers only drafts: documents, payments, payroll, payouts, depreciation, closes post inside `Ledger` commands; the GUI string-matches the origin label (`ledger-gui/src/books.rs:708`). Fix: `ledger_core::evidence::requirement(&Ledger, &JournalEntry, origin) -> Required | Exempt{source}` with `EntryOrigin` derived at replay from the owning event; one private `Store::evidence_gate` called from every Store command that posts; at-once tools take `upload_id|attachment_id`; move `ReceiptPolicy` into `ledger_core::settings`; GUI calls the same function over the Snapshot; delete the origin-string heuristic.
- **A11.** Recovery as "reverse the write-off, dated now" misstates Form 1065 lines for a prior-year write-off (§111: gross income, not a negative deduction). Fix: `Movement::Recovery`: Dr 1200 [d, Recovery] / Cr a `bad_debt_recoveries` revenue role; `written_off` nets Recovery.
- **B1.** `preview_token` as defined cannot cover confirm tools without a draft entry (close, reconcile, dispose, runs, the create tools). Fix: two shapes, `Draft{…}` and `Operation{tool, canonical_args, journal_head_seq, settings_seq}`; a head advance refuses `Operation` tokens; state plainly it protects against drift only.
- **B3.** The Lean post step must carry the reversal preconditions or `reversal_reverses_posted` is provable for a model admitting A2's counterexample.
- **C1 (also Z-4).** `ledger-view`/`BooksView` duplicates `PackageBooks`; a crate depending only on ledger-core cannot implement the trait for Store/Snapshot. Fix: grow `PackageBooks`; attention and `next_steps` beside the package builder.
- **C2.** Jobs are not lease holders: the books can close under a running audit or backfill. Fix: a job holds a lease of kind `job`; close waits or cancels with a recorded reason.
- **C3.** `books.audit` in `ledger-core` cannot do integrity/attachment/backup checks (no I/O); "an `audit.run` at the current head" is unsatisfiable once the audit's own event advances the head. Fix: split core statements audit / store integrity (reuse `verify`) / server composition; define "current head" as no figure-bearing event after `through_seq`.
- **C4 (also Z-5).** P6's seccomp/Landlock extractor and `inline_base64_requires_sha` contradict D-32 and D-46. Fix: crash isolation only (separate process, rlimits, timeout), named as such; one extractor behind both `text_of` callers.
- **D1.** No alternatives weighed for P4, P4b.3, P5, P6, P8 (derived vs stored figures; HMAC token vs server-recorded last preview; new crate vs `PackageBooks`; subprocess vs `catch_unwind`; direct write-off vs allowance). P4, P5, P6, P8 have no risks list.
- Nits: A12 "or a later control role" would break payroll if read literally (code restricts to receivables/payables); A13 `max_discount`, `q×u`, `exact` are money arithmetic outside the kernel; A14 the previewed number can differ from the confirmed one (token must cover next number and head); A15 the B-10 exception (reversing the reversal of a legacy hand control line is refused) is in rules.md B-38 item 3 but not the plan; B4 `digest since_seq` needs `entry_meta` replay P4 omits; C5 `consolidate_duplicates` may withdraw an entry's only evidence; C6 extraction records tie the chain to extractor version.
### Structural (ST)
- **F6.** Four conventions for superseded text and two stacked precedence rules (5, 997). Live scope still describes removed components with no marker (851, 1759-1760, 1788, 1808, 1817, 1686). Fix: one rule in a "How to read this document" section: strike-through + "→ D-n"; fixed-heading `**As built (date, commit):**` blocks; top decisions become rows D-1..D-4, D-6, D-8, D-14.
- **F7.** P4 placed before P4b though P4b built first and P4 depends on it. Keep labels; order §8 by status then merge order; status table at the head.
- **F8.** Decisions not discoverable from phases: no Status/Affects columns; phases rely on unnamed decisions (list per phase in the report). Fix: columns; "Decisions in force" per unit; reverse index 10.4.
- **F9.** 10.3 is answered history titled as open questions. Fix: retitle closed; Q-1..Q-3 become D-3/D-4/D-2 rows; crumbs checklist into P2's as-built.
- **F10.** Missing: reading guide/glossary (S:, I-n, B-n, D-n, Q-n, R-n, T1/T2, gap n, slice n, defect n), defect index 1-24 with fixing phase, P2 slice index, dependency graph, ownership map, merge order.
- **F11.** §8 preamble (771) still mandates mutation testing (D-33). Fix: "records in docs/testing.md what P12 must mutate".
- **F12.** Risks split; §9 mixes retired risks (2, 4, 5, 13). Fix: per-unit Risks; §9 cross-cutting only; retire closed ones.
- **F14.** Cross-reference errors: 1147 "(P4b.10)"; 897 defect list omits 23/24; 53 and 1785 stale counts; 1899 "P1 says so" (3.9 does); 1007 the ask placed after the answers.
- Nits F15 (separators, heading depth, "the the first company"), F16 (no Goals/Non-goals heading).
### Style (SY)
- **M1-M4.** "draft" (P4 draft entry vs preview PDF), "document" (four senses), "slice" (P2 slice n vs P4b Sn), "record" used ambiguously. Fix: "preview" for invoice previews; define document = invoice or bill; always qualify slices.
- **M5.** Built/unbuilt/stale text unmarked across the range (list in report). Fix: status tag per bullet; imperative for unbuilt.
- **M6.** Line numbers disagree and use "~". Fix: symbol names only.
- **M7.** Hedges to resolve: "once P4 lands" (1236, 1285); "about 1,800 characters"; "if it grows"; "if ever needed"; "or a later control role"; "About 7 new tools" (eight listed); "for now"; "if the company qualifies"; "`egui_term`, or our own"; "for a set time"; "the documented maximum"; "or both"; "perhaps"; "once it stabilizes"; "after the highest legacy id"; "for one release".
- **M8.** Owner-facing prose inside P4b.1 hides rules (aging boundary 1035, sent date 1041, UCC warning 1051, handbook list 1054, the gross-method DECISION 1042). Fix: a "Rules derived from P4b.1" list; owner quotes move to §10.
- **M9.** SHOULD/refused/NEVER inconsistent in spec blocks. **M10.** Long semicolon lists (974, 976-977, 1002, 1003, 1097-1104, 1330, 1594, 1597, 1649, 1744, 1805-1812, 1818, 1827, 1864). **M11.** No Accept per P4b slice. **M12.** Bill line `account` optional/required/"as before" stated three ways. **M13.** "Re-created on DATE" vs determinism (DATE = `as_of`). **M14.** Manual screenshot step unnamed. **M15.** Private change log and goldens paths. **M16.** Features vs upgrades count. **M17.** P10 T1/T2 stated twice, undefined. **M18.** Subjective criteria. **M19.** Unexpanded abbreviations. **M20.** Verbless scope fragments. **M21.** DECISION vs CHOICE vocabulary. **M22.** bookspec test placement.
- Nits N1-N15: tool-name formatting, four names for the control check, `reminder_days` vs `remind_days`, date/range/unit forms, pseudo-Rust blocks, `a | b` notation undefined, heading 1573 carrying a procedure, informal voice, redundancies, garbled sentences (1836, 1819), ambiguous "or" (1362), unexplained "informational"/"2100 aging".
## The per-unit template (structural reviewer; adopted for the rewrite)
Status | Depends on (merge first; owner go-ahead per D-39) | Runs in parallel with | Blocks | Decisions in force | Defects closed | Feature flag(s) — then **Build** (Core, Store/journal, MCP, Viewer, Exports/handbook, First-company steps; each bullet ends with `crate/path::symbol` new|changed), **Owns**, **Touches shared**, **Proofs**, **Tests** (every test named; bookspec ids; the docs/testing.md P12 entry), **Docs**, **Done when** (numbered, checkable by the EM), **Risks**, **Open questions for the owner**, **As built (date, commit)**.
## Proposed §8 table of contents (structural reviewer)
8.0 How work is organized (8.0.1 status and dependency table; 8.0.2 dependency graph and merge order; 8.0.3 shared files and merge order; 8.0.4 every unit's bar) · 8.1 Landed (P0, P1, P2 with slice index, P3, Projects, P4b S1-S3; as-built blocks kept) · 8.2 Remaining, in merge order (P4b S4, P4, P4b S5, S6, S7, P5, P6, P7, P8, P9, P10, P11, P12, P13; each in the template) · 8.3 P4b reference design (former P4b.1-15, 17) · 8.4 Not scheduled.
@@ -0,0 +1,9 @@
# Goldfish Annotated Review Report
Status: resolved (2026-10-26)
**Target:** `plan/served.md` (full file; the remaining work, lines 766-2058 of the file as of `c1f9001c`)
This review ran in uninterrupted mode. The findings of all nine stages, their CONTEST / PROPOSE FIX resolution, and the traceability from finding to action item are in `consolidated_review_report.md` in this folder (the "Action items, by priority, with resolution" and "Contested (not applied)" sections). Every PROPOSE FIX was applied to `plan/served.md` on 2026-10-26; the file's new section 0 ("What changed on 2026-10-26") and section 8.0 describe the result.
Counts: 13 blocker action items (all applied), 10 major (all applied, two in part: M10's style pass covers section 8 only; M5's strike-throughs cover sections 1 to 7 and 9), nits applied where section 8 was rewritten. Contested: 9 mechanisms from the design critic, replaced by the simpler resolutions of the Stage 8 report and recorded as deferrals (D-49) or procedures; the 17-field template (8 fields adopted); "minisign only" (asked as Q-4 instead).
@@ -0,0 +1,123 @@
# Stage 9: Implementation readiness and codebase details (`plan/served.md` 766-2058)
Reviewers: `egm-implementation-readiness` (IR), `egm-codebase-details` (CD). 2026-10-26. Additional to Stages 5-8. Symbols, not line numbers. Sizes assume an entry-level Rust engineer new to this codebase; one engineer-day = one reviewed PR, `just check` green, 200-400 lines including tests and docs.
## Cross-cutting
### IR-CC-1 Blocker. The mechanical recipes every unit repeats are nowhere written down
**R-A New journal feature.** (1) `crates/ledger-store/src/format.rs`: `pub const NAME: &str = "name";` with a doc comment saying what an older version would misread; append to `KNOWN_FEATURES` (`Store::ensure_feature` has `debug_assert!(KNOWN_FEATURES.contains(..))`). (2) Unit test `name_is_its_own_feature` beside `document_standing_is_its_own_feature`: `declared_knowing` with every earlier feature gives `NewerJournal`, `require_feature` gives `FormatUpgradePending`, `upgrade().adds()` names it. (3) Every `Store` command writing the marked line calls `self.ensure_feature(NAME)` before `rehearse`/journaling; store test `<write>_waits_for_<name>` modelled on `repaying_after_a_reversal_waits_for_document_standing` (`tests/arap_standing.rs`): refused, journal unchanged, succeeds after `upgrade`, replay equals live. (4) `crates/ledger-cli/tests/format_upgrade.rs::books_p2_served_cut_over_whole`: one entry in the `refused` list and the same call after consent. (5) `handbook/format-upgrade.md` and rules.md B-27 list the feature. Feature names are allocated in the plan; no engineer invents one.
**R-B New event type.** (1) `format.rs` `EVENT_TYPES` (alphabetical; `tests/format.rs::every_event_the_store_replays_passes_the_gate` scans store.rs for every `"x.y" =>` arm and fails on a mismatch either way). (2) A payload reader in `ledger-store/src/meta.rs` in the style of `project_set_of`: missing key → `corrupt`, unknown enum value → `format::newer_content`. (3) A `replay_line` arm in store.rs calling a `Ledger::replay_*` method (`line.seq` is available). (4) An `apply_line_rows` arm (projection SQL); a new migration in `schema.rs` and `SCHEMA_VERSION` 15 → 16 only when a table or column changes; `tests/schema_files.rs`. (5) `projection_rows`/`compare_projections` list the new table; `Ledger::differs_from` gains the new state (the `("line_docs", ..)` pattern). (6) `fuzz/fuzz_targets/fuzz_store_replay.rs` `const TYPES` (`the_replay_fuzzer_writes_every_event_type` fails otherwise). (7) A `describe` arm in `ledger-gui/src/pages/journal.rs`. (8) rules.md and `docs/receipt-store.md` event table.
**R-C New MCP tool** (one commit; three tests fail on a partial state). (1) `types.rs` params/out structs with `JsonSchema` and doc comments (they are the schema descriptions). (2) Handler in `server.rs` or `tools/<group>.rs` with `#[tool(name, title, description, annotations(read_only_hint, destructive_hint, open_world_hint))]` (`annotations_complete`). (3) `scopes.rs::TABLE` row and its array length constant (compile error otherwise). (4) `docs/mcp-interface.md` entry in the shape `` `books.x { a, b? } → { c }` `` (`every_tool_is_documented_and_every_documented_tool_exists`). (5) Description ≤ 700 chars (`tool_descriptions_fit_700_characters` pins a count `OVER`; P5 drives it to 0), `tool_text_lint`. (6) Over-the-wire test in `crates/ledger-cli/tests/` through `common::Books::new(tag)` → `.wire()` → `.call(tool, json)` → `answer_of`. (7) `docs/parity.md` row.
**R-D New setting key.** (1) `ledger-core/src/settings.rs` `KEYS` entry `Key { name, toml, kind, sensitivity, doc }` (pattern `company.duns`); a TIN exemption is a `Kind` arm, not a key match (`no_tin` is applied by every text `Kind`; `Kind::Duns` escapes by having its own arm). (2) `setting_feature` arm in `ledger-store/src/format.rs` (`pub(crate)`; `Store::journal_settings` calls `ensure_features(setting_feature(..))`; the `"settings.set"` replay arm refuses an unknown key as `newer_content`). (3) `ledger-config` `toml_round_trip_zero_events`, `codec_props.rs`; `settings_units.rs` for the canonical form. (4) The settings.rs INVARIANTS block.
**R-E New handbook section.** `crates/ledger-mcp/handbook/<id>.md` plus a tuple in `handbook.rs::SECTIONS` (`include_str!`); placeholders like `{document_projects}` via `document_projects_rule(declared)` give different text before and after the owner's upgrade; the tests in handbook.rs check every section renders and ids are unique.
**R-F New Needs-attention item.** Until the attention engine moves, an item is written twice: `ledger-gui/src/attention.rs::find` (and its GOAL block's INVARIANT 2 list) and the server's `books.start` next steps in `tools/kept.rs::start_out`. Parity test `ledger-gui/tests/view_model.rs`; screenshot through `shot()` (`ledger-gui/tests/invoices.rs`).
**R-G** A `**For P12 (mutation targets):**` paragraph in `docs/testing.md` naming each new function's arms.
### IR-CC-2 Major. `docs/testing.md` has no section for S1 part B, S2 or S3 (last heading "P4b S1, part A"). P12's input is three units short. 0.5-day pre-step.
### IR-CC-3 Major. The journal-safety bar for a unit that writes new lines is not a checklist. "8.0.5 Before a journal-touching unit merges": (a) `ledger-store/tests/store.rs::books_written_under_older_rules_still_open` gains a hand-written line of the new shape via `JournalWriter`; (b) a new fixture under `tests/fixtures/old-books/` written by the version before, plus `expected.json`; (c) the `books_p2_served_cut_over_whole` refused entry; (d) the `_is_its_own_feature` test; (e) `tests/format.rs` arms; (f) a `figures.json` golden from the previous version where a figure could move (pattern `tests/fixtures/p4-arap-before`, `arap_standing.rs::write_the_p4_arap_fixture`); (g) "merge does not need the owner's go-ahead; the live books refuse the new write until he gives it".
## P4b S4 `documents_v2`
- **CD-A1/A2.** How a document is journaled today: `Store::create_invoice(customer_id, date, due_date, lines)`: `rehearse` → `approval_gate` → `arap::validate_lines`, `arap::validate_due_date`, `closed_period_containing` → `ensure_document_lines` (calls `ensure_feature(DOCUMENT_LINE_PROJECTS)` only if any line has `class_id`) → `ensure_control_account(&RECEIVABLES)`/`(&REVENUE)` (bills `GENERAL_EXPENSES`, `PAYABLES`) → posting from `arap::document_parts` → `journal_auto_lines` (`entry.created`, `entry.line_added`×n, `entry.posted`) → `append_journal("invoice.created", {id, customer_id, date, due_date, lines: [document_line_json], entry_id})` → `self.ledger.create_invoice(..)` → `apply_line_to_sqlite`. The feature pattern is `ensure_document_lines` + `document_line_json` (omits `None` keys: byte-identical old shape) + `parse_document_lines` (absent/null → None). The posting is built twice: in `Store::create_invoice` and in `Ledger::create_invoice_steps` (`ensure_control`, `add_tagged_line(.., DocTag{Origin})`, `add_document_parts`); `Ledger::replay_invoice`/`replay_bill` re-derive and compare (`ReplayMismatch`). Line accounts change: `arap::document_parts` key → `(Option<AccountId>, Option<ClassId>)`, `add_document_parts`, both `*_steps`, both `Store::create_*` posting vectors, both `replay_*`. `check_document_line_account` goes beside `Ledger::check_line_account`, reusing `control_role_of` and the `cash_accounts` setting.
- **CD-A3.** `arap::validate_due_date(date, due_date)` (calls `calendar::check`, refuses `DueBeforeDate`) is what `Terms::due_date` feeds; `arap::validate_lines` is where `quantity × unit_price == amount` goes.
- **CD-A4.** MCP today: `CreateDocumentParams { party_id, date, due_date (required), lines: Vec<DocumentLineParam{description, amount, class?}> }`, `CreateDocumentOut { id, total, entry_id, warnings, notes }`; `BooksServer::create_invoice` → `document_lines(&store, p.lines)` → `store.create_invoice(..)` → `created_uncategorized` → `document_notes`. `resolve_vendor(store, &str)` exists (id or name, ambiguity refused); add `resolve_customer` mirroring it. State the `due_date` fallback: `terms` arg → party `default_terms` → `invoicing.default_terms` → refuse.
- **CD-A5.** Preview pattern: `BooksServer::post_entry` with `confirmed: Option<bool>` returns the full `out` unchanged when not confirmed; `preview_lines_for(store, &[(AccountId, Side, Money)])` computes before/after balances; engine probe = `Store::rehearse` (`let mut probe = self.ledger.clone()`), so `Store::preview_invoice` runs `Ledger::create_invoice` on a clone. Settle `confirmed: bool` (run_schedules) vs `Option<bool>` (post_entry, reverse_entry, dispose_asset, reconcile).
- **CD-A6 Blocker.** "`issued_pdf` in `invoice.created`" cannot be done in the plan's event order: `Store::attach` allocates the id by `MAX(id)+1` and needs the entry to exist. Choose (b): no `issued_pdf` field; the filed copy is `receipt.attached` on the entry plus `attachment.linked` to `Subject::Invoice(id)` (`Store::link_attachment`, `attachments_for(Subject)` already read links); the sha is already in `receipt.attached`'s `blob_sha`. Render (`invoice_pdf(&InvoiceDoc)`) before `journal_auto_lines`. Reuse `Store::attach_receipt(Some(entry_id), "INV-0007.pdf", "application/pdf", &bytes, Some("Issued invoice"))`.
- **CD-A7.** Settings: `KEYS`, `Kind` arms; `invoicing.payment_methods` needs `Kind::PaymentMethods` whose arm validates the list and skips `no_tin` for `lines` of `ach`/`wire` methods only; each key an arm in `setting_feature`; `Settings::company().profile` (`Vec<(toml_key, value)>` of `company.*` text keys) is how `invoice_document` finds the address, so seller email/website are new `company.email`/`company.website` keys.
- **CD-A8.** `party.updated {id, change: PartyField}`; `PartyField` is `#[serde(tag="field", content="value")]` with `Name, DisplayName, Website, Address, Notes, Archived`; `Ledger::check_update_party` applies `parties::check_text` (TIN scan). The replay arm maps an unknown `field` to `JournalCorrupt`, not `newer_content`: S4 must fix that (precedent `meta::project_set_of`), add `PartyField::Email`, `PartyField::DefaultTerms`, gate with `ensure_feature(DOCUMENTS_V2)`, extend `UpdatePartyParams`, add a `describe` arm.
- **CD-A9.** `ledger_package::invoice_document(ledger, company, id, as_of, today) -> Result<InvoiceDoc, String>` builds `InvoiceDoc { number ("Invoice {id}"), issue_date, due_date, terms: None, seller: InvoiceParty, bill_to, items: Vec<InvoiceItem{description, amount}>, total, settled: Vec<(String, Decimal)>, balance, stamp: Option<InvoiceStamp>, stamp_line, note }`; `InvoiceStamp { Paid, PartiallyPaid, PastDue, Void, Draft }`; `templates/invoice.typ` prints seller, INVOICE + number, bill-to, dates, Terms row, lines (description, amount), Total, settled rows, Balance due, stamp, note; `paper: "us-letter"`. **New in S4:** quantity × unit price column, customer PO, payment methods + QR, memo, footer, seller email/website, the issued stamp-less copy, the `terms` string.
- **CD-A10.** QR: `labels.rs::qr_svg(tag) -> Result<String, PdfError>` and `templates/labels.typ`'s `image(bytes(l.qr), format: "svg", ..)`.
- **CD-A11.** S1 kept `amount_paid`, `owed`, `status` on `Invoice`/`Bill` as derived caches (`Ledger::refresh_doc`), never journaled; the full status is `arap::DocStatus { Open, Partial, Paid, WrittenOff, Void, Overpaid, Unattached }` via `arap::doc_status(origin_stands, origin_tagged, total, outstanding, written_off)`; `ControlCheck.attention: Vec<DocAttention>` lists Overpaid/Unattached. The plan's machine omits both.
- **CD-A12.** Handbook mechanism: `handbook.rs::SECTIONS`, `section(id, settings, currency, document_line_projects)`, `books://handbook/{id}`. S4 adds `invoices-and-bills.md` and a `{documents_v2}` placeholder.
- **CD-A13.** `create_bill` with a file: the upload path is `tools/files.rs` (`begin_upload`, `hand_over`) and the `upload_id`-to-bytes helper used by `books.attach_receipt`; category `receipts`, filed on the bill's entry, linked `Subject::Bill`.
- **IR-S4-1 step order** (each `just check` green; four PRs: 1-4, 5-6, 7-8, 9-11): 1 core `Terms` types + `Terms::describe()`, tests `terms_derive_due_dates`, `discount_bounds_and_late_reason`, Kani `terms_due_date_laws`; 2 optional fields on `Invoice`/`Bill`/`DocumentLine` with `#[serde(default, skip_serializing_if)]` as `class_id`, `PartySnapshot`, `check_document_line_account`, `document_parts` by `(account, class)`, q×u rule "compare as `Decimal`, no rounding; mismatch is `LedgerError::LineArithmetic`", tests `bill_lines_post_to_their_accounts`, `qty_times_price_must_equal_amount`, `invoice_line_to_deferred_revenue_posts_to_2400`; 3 numbers and duplicates (`next_invoice_number(settings)`, `(vendor, normalized vendor_ref)`), tests `numbers_run_and_voids_keep_theirs`, `duplicate_vendor_ref_refused`, `first_number_locked_after_first_numbered_invoice`; 4 settings (R-D) and party fields; 5 store (R-A, R-B): `DOCUMENTS_V2`, `ensure_feature` only when any new field is `Some`, `no_new_fields_journals_byte_for_byte`, `documents_v2_waits_for_the_upgrade`, projection columns (schema 16), `books_p2_served_cut_over_whole` entry; 6 issued PDF filed in the issuing call (`issued_pdf_is_filed_with_the_invoice_or_nothing_is`); 7 MCP `CreateInvoiceParams`, `customer` resolution, `party_id` alias until P5's doc rewrite, preview returning the number and a DRAFT `HandedFile`; `documents.rs::an_invoice_from_issue_to_paid` (S4 leg), `documents_v2_refused_until_consent`; 8 `create_bill` with `vendor_ref`, `received_on`, `terms`, `upload_id | existing_attachment_id` (refused when both absent under `documents_v2`), `duplicate_reason`; `duplicate_vendor_invoice_refused`; 9 PDF delta with golden sha in `ledger-import/tests/spec_import.rs` beside `pdf_is_deterministic_and_labels_are_data`, `invoice_pdf_prints_what_it_should`; 10 viewer (`shot("invoice-numbered")`, `view_model.rs`); 11 handbook (R-E), rules.md B-39, B-40, parity, For-P12. S6 may start after step 1 merges.
- **IR-S4-2.** Must stay green: `legacy_documents_replay_with_tags`, `legacy_reversal_now_counts`, `a_payment_journals_its_method_and_reference`, `several_payment_events_on_one_entry_tag_its_lines_in_order`, `the_list_shows_the_ledgers_figures`; `an_invoice_renders_deterministically` golden updated once with the recomputation in the commit; no bookspec example changes.
- **IR-S4-3/4.** Rollback: an older version derives the posting without `account` and hits `ReplayMismatch`, which is what the gate prevents (CC-3 a-e). No figure moves. External: D-39 go-ahead for live use only. Size 12-16 days.
## P4b S5 `invoice_sent`
- **IR-S5-1.** Depends on S1 only; if merged before S4, `to` is required and the reminder uses 14 days fixed; after S4 the defaults apply.
- **IR-S5-2.** Decisions to write: legacy invoices exempt from "never sent"; predicate `status ∈ {Open, Partial} ∧ sends.is_empty() ∧ today(company.timezone) > date + 2 days`; `mark_sent` on Void refused, on Paid allowed; bills get no sends; `Sent` gains `by: String`, `reference: Option<String>`, `SentVia::Other(String)`.
- **IR-S5-3 order.** 1 core `Sent`, `Invoice.sends`, `Ledger::mark_sent`; `a_send_changes_no_figure`, `mark_sent_on_void_refused`; 2 store (R-A, R-B) `INVOICE_SENT`, `invoice.sent`, projection `invoice_sends`, `differs_from`; `mark_sent_waits_for_invoice_sent`, `sends_replay_and_project`; `arap_props.rs` gains `MarkSent`; 3 MCP `books.mark_sent` (W); `documents.rs::mark_sent_records_who_and_how`; 4 attention (R-F); 5 handbook, docs, For-P12. Size 3-4 days.
## P4b S6 `settlements`
- **IR-S6-1 / CD-B2.** Exists: `Movement::Discount`, `DocState.discounted`, `kernel::running_bounds`/`bounds_kept` with Kani `bounds_check_matches_definition` and `bounds_kept_is_the_definition_on_books_in_bounds` (the plan lists the first as to-build). `Store::ensure_payment_features(doc, amount, details)` is the gate for payment events (adds `settlements`); `Store::receive_payment_with` uses `journal_auto_entry` (two lines); multi-document needs `journal_auto_lines`. `PaymentParams`, `PaymentDetails { instrument, reference }` (`MAX_REFERENCE_CHARS = 200`) are what `applications[]` extends. `AgingParams { as_of }`, `AgingRowOut` have no `detail`/`control`. Export seams: `hand_over_export`, `layout_statement`, `ledger_import::report::{format_amount, amount_parts}`, `pdf::statement_pdf`; CPA README assembled in `ledger-package/src/package.rs`.
- **IR-S6-2.** Replay for multi-document/discounted entries: legacy single-document events keep the positional rule (`several_payment_events_on_one_entry_tag_its_lines_in_order`); under `settlements` lines are named explicitly (Stage 8: `line`/`discount_line` indices on the existing events); unmatched lines listed by `control_check`; `a_settlement_names_its_lines_and_replays`.
- **IR-S6-3.** Kernel: `kernel::settle(applications, discounts, fee)` and `kernel::discount_max(total, percent_bp)`; Kani `settle_is_exact`, `discount_max_exact`; bridge properties in `tests/kernel_bridge.rs` (omitted by the plan).
- **IR-S6-4.** Roles (`purchase_discounts`, `sales_allowances`, `payment_processing_fees`) via the on-demand creation used by `receivables`/`payables`; `templates.rs::no_template_puts_another_account_at_a_control_code` stays green. Aging CSV columns `number, party, date, due_date, terms, total, paid, discounted, outstanding, bucket, days_overdue`; tie-out line `TOTAL,…,<Σ outstanding>,GL <account> <balance>,difference 0.00`. `books.bills_due` (R): rows `{bill, vendor, vendor_ref, due_date, outstanding, discount_by?, max_discount?}`, footer `by_date: [{date, amount}]` running sum.
- **IR-S6-5 order.** 1 kernel + Kani + bridge; 2 core `Ledger::settle`, tests `one_payment_settles_two_invoices`, `discount_bounds_and_late_reason`, `fee_splits_gross_fee_net`; 3 store `SETTLEMENTS` (R-A), line indices (R-B), `discount_reason` journaled, `settlement_waits_for_the_upgrade`, `arap_props.rs` ops, cut-over entry; 4 roles/templates; 5 MCP `applications[]`, `fee?`, `paid_in_full_marking?` (bool), `books.bills_due`, aging `detail`/`control`; over-the-wire `one_payment_settles_two_invoices`, `bill_on_terms_with_discount`, `card_paid_bill_is_not_1099_nec`, `equipment_bill_payment_is_investing_cash`, `bills_due_orders_by_due_and_discount`, `multi_document_payment_counts_once_for_1099`; 6 exports and CPA package; 7 viewer; 8 handbook, docs, For-P12, bookspec. Depends on S4 step 1. Size 8-10 days.
## P4b S7 `write_offs`
- **IR-S7-2.** Depends on S4 step 6 (attach inside a document command): `write_off_invoice {…, upload_id | existing_attachment_id}` required; `LedgerError::WriteOffOnCashBasis` when `books.accounting_basis = cash` (`Company.basis` from `Settings::company()`).
- **CD-B3.** Exists: `Movement::WriteOff/Adopted/RoleMove`, `DocStatus::WrittenOff/Unattached`, `arap::UnattachedLine`, `ControlCheck.unattached`, `Ledger::check_assign_role` (SCENARIO 6 `RoleAccountHasHandLines`), `Store::assign_role` journaling `role.assigned`, `Ledger::control_role_of`.
- **IR-S7-1/3, CD-B5.** If adoption/role moves were built: `differs_from` must compare `line_docs` directly; Lean `Documents.lean` (`TLine`, `gl`, `docSum`, `unattached`, `Step := document | reverse | assign | release`, `Reachable.control_is_documents`) has no bounds notion and no adopt constructor. (Stage 8 defers both tools; S7 = `write_off_invoice` + `bad_debts` role + B-41 + Lean `Step.writeOff`.)
- **IR-S7-4 order (cut).** 1 core `Ledger::write_off`; `half_paid_rest_written_off_is_written_off`, `write_off_refused_on_cash_basis`, `write_off_then_recovery`; 2 Lean `Step.writeOff`, `control_is_documents` re-proved; 3 store feature + event (R-A, R-B); 4 MCP `books.write_off_invoice` (destructive); `documents.rs::write_off_then_recovery`; 5 attention, viewer; 6 handbook step 4, B-41, bookspec `bad-debt-direct-write-off`. Size 4-6 days (8-10 with adoption and role moves).
## P4
- **CD-C1.** Drafts: `EntryStatus { Draft, Posted }`, `JournalEntry { id, date, description, lines, status, reversal_of }` with `pub(crate)` mutators; `Ledger::posted_entries()` iterates `order`; drafts only in the `entries` map. A draft state is a new field + `differs_from` + projection column + `entry.draft_state_set` arms.
- **CD-C2.** `Ledger::reverse_entry_steps`: `check_reversal` → `create_entry` → `replay_add_line` (flipped) + `set_line_class` + `tag_line` → `mark_reversal_of` → `post_entry` → copies `entry_vendors`, `entry_instruments`, `entry_parties`, `entry_no_party` → schedule `posted` set → `refresh_docs_of`. (Stage 8 keeps reversals immediate, so this stays.)
- **CD-C3.** The gate today: `ledger_config::ReceiptPolicy::receipt_required(&Ledger, &JournalEntry)`, `describe()`, `rule(currency)`; callers `BooksServer::post_entry` (`ReceiptRequiredError { code: "receipt_required", remedy_tool: "books.attach_receipt", split_group }`), `digest`, `dispose_asset`, `handbook::evidence_rule`, kept.rs `RulesOut.evidence`; GUI `Books::receipt_rule()` and `missing_receipt`. `Store::post_entry` checks only `approvals_missing`.
- **CD-C4.** `BooksServer::digest(DigestParams { since })` filters by entry date; `DigestEntry`, `totals_by_account`, `stale_rules`, `tail_repairs`, `unreconciled_accounts`, `uncategorized_accounts`, `control_problems`, `settings_problems`. `posted_seq`: add an `entry_id → seq` map filled in the `"entry.posted"` replay arm (precedent `Ledger::apply_settings(.., line.seq)`), no new event.
- **CD-C6.** Tools taking `confirmed` today: `post_entry`, `reverse_entry`, `run_schedules`, `dispose_asset`, `reconcile`, and the posting runs.
- **IR-P4 order (after Stage 8's cut).** 1 draft states, `list_drafts`, `discard_draft`, `set_draft_state`; `discard_unblocks_close`; Lean `Step.discard`; 2 gate unification: policy evaluation moves from `ledger-config` to `ledger-core` (`evidence::requirement(&Ledger, &JournalEntry, &Settings)`), `Store::post_entry` calls it, GUI deletes the string heuristic (`gui_and_server_gate_agree`); 3 digest `since_seq`, `as_of`, drafts by state (`digest_since_is_posting_time`); 4 `preview` digest at the envelope, required by every confirm tool (`stale_preview_refused`); 5 attention (R-F), handbook `drafts-and-reversals.md`, rules, For-P12. Feature `drafts_v2` in S7's upgrade batch; schema bump for `entries.draft_state`, `entries.posted_seq`. Size 8-10 days.
## P5
- **CD-D1.** `books.start` v1: `StartParams { focus }`, `start_out -> StartOut { book, head: HeadOut { seq, hash }, state, rules, settings_summary, conventions, open_items, projects, truncated, handbook_sections, format_upgrade, next_steps: Vec<NextStepOut { tool, args, why }>, isolation }`, `START_MAX` fit loop. v2 extends these.
- **CD-D2 Blocker.** Prompts: `opening.rs::capabilities()` enables tools and resources only; no `#[prompt_router]`, `list_prompts`, `get_prompt`, `list_resource_templates`. P5 adds them and the bridge's cold-start `opening_initialize` must advertise the same.
- **CD-D3.** `tool_descriptions_fit_700_characters` asserts `over == OVER`; P5 drives `OVER` to 0. `handbook::CONNECT_MAX = 1400`, `CONNECT_LEAD = 512`.
- **CD-D4.** `journal_history`: reuse the `describe` arms of `ledger-gui/src/pages/journal.rs` by moving them to `ledger-store` beside `meta.rs`; read with `journal::read_journal_and_format` + `Store::tail_repairs`.
- **CD-D5.** Source-only revision: refusal in `Ledger::check_convention`; `Source { decided_by, decided_on, citation, note }`; new event `convention.source_revised` + feature.
- **IR-P5-1 Blocker.** The result envelope touches every handler and every wire assertion: single owner, merged before or after every other MCP-touching branch (the EM writes which in 8.0.3).
- **IR-P5-2.** Agent evals: `crates/ledger-cli/tests/agent_evals/*.json` `[{"call": "books.start", "args": {}}, {"expect_next_steps_contains": "books.set_setting"}, …]`, one test `agent_evals_follow_only_next_steps`.
- **IR-P5-3 order.** 1 envelope; 2 guard registry enum with `remedy_tool`; 3 `books.start` v2; 4 prompts and resource templates with golden renders on the `p2-served` fixture; 5 `journal_history`, source-only revision; 6 descriptions ≤ 700, handbook complete; 7 `docs/mcp-interface.md` rewrite; 8 agent evals. Size 10-12 days.
## P6
- **CD-E1 Blocker.** "Identical bytes return the existing record" contradicts `Store::attach_receipt` INVARIANT 3 ("each filing MUST be its own attachment"); `attach` always allocates a new id even when `BlobStore::put` reports `deduplicated`. P6 must state the invariant change and that legacy duplicates stay.
- **CD-E2.** Consolidation sequence: `link_attachment` the first record to each subject, `unlink_attachment`/`unlink_receipt` the duplicate, then `withdraw_attachment(dup, "duplicate of #N")` (it refuses while linked: `AttachmentLinked`, `AlreadyLinked`).
- **CD-E3.** Correction to earlier stages: `ledger-gui/src/receipts.rs::text_of` is UTF-8 text only; PDF rendering is the private `pdf_first_page_png`/`render_pdf` (hayro 0.8, first 30 pages); the only PDF text extraction is a test helper in `ledger-import/tests/spec_import.rs` using `hayro_interpret` `GlyphRun` (dev-dependency); `tin_files::text_of` never reads a PDF. P6: promote the reader to `ledger_import::pdf_text(bytes)`, move the rasterizer to `ledger_import::page_png(bytes, page, scale)`, call both from `tin_files::text_of` and the viewer. No lopdf.
- **CD-E4/E5.** `GetReceiptParams { attachment_id, view: "meta" | "bytes" }`: `text`, `page_image` are new arms (`HandedFile` via `hand_over`). Categories validated in `BooksServer::file_document`; `Attachment.category` doc lists `receipts, formation, legal, tax, accounting, other` while descriptions already use `statement`, `import_source`; new categories (notices, plans, history; P8's `workpaper`) extend that one place.
- **IR-P6-1.** Backfill = `wanderbooks extract --book ID` over the admin socket, idempotent on `(attachment_id, extractor_version)`.
- **IR-P6-2 order.** 1 `ledger-import::extract::{pdf_text, page_png}` behind both callers; `extraction_deterministic`; `fuzz_extract_pdf`; 2 event `attachment.text_extracted` + feature `attachment_text`; 3 `get_receipt` meta+text, `page_image`; 4 dedup remainder (`find_attachment`, `consolidate_duplicates`, `attachment.withdrawn {duplicate_of}`, the "only evidence" refusal); 5 annotations and receipt numbers; 6 backfill command; 7 D-20 categories. Accept proxy: every attachment of the `p2-served` fixture gives `get_receipt` < 8 KB. Size 8-10 days.
## P8
- **CD-F1.** Exists: `Store::verify_and_heal() -> VerifyReport`, `verify()`, `verify_blobs()`, `Snapshot::verify_blobs()`; `BooksServer::verify` already appends `control_check(NaiveDate::MAX)`; access log `ledger_store::access::{AccessLog, read(book_dir) -> (Vec<Recorded>, Head)}`; `CashFlowStatement.reconciles`. Drop "vault public-key sha" (P7 superseded).
- **CD-F2.** "Closes in order" is an engine refusal already; member deltas `kernel::apportion_cumulative`; schedules `Schedule::due/next/remaining`; `FixedAssetRegister::unposted_depreciation()`; the audit calls these.
- **CD-F3.** Existing problem lists to convert: `settings_problems`, `control_problems`, `unreconciled_accounts`, `tax::uncategorized_accounts`, `rules::stale_rules`, `Backups::standing` + `needs_attention()`, `drill_due`; no shared `Finding` type: P8 defines one in `ledger-core`. `Subject::Period(u64)` for linking the workpaper; `Store::file_document(category, ..)` to file it.
- **IR-P8-1 Blocker.** A table `check | predicate (function) | finding code | fixture (how seeded) | severity`, one row per check; fixtures built in-test through `Store`; `audit_detects_each_seeded_defect` iterates it.
- **IR-P8-2 order.** 1 `ledger_core::audit::statements(&Ledger, through) -> Vec<Finding>`; 2 `Store::integrity_findings`; 3 server composition, `audit.run` event + feature `audits`, `workpaper` category; 4 workpaper JSON + Typst (`templates/workpaper.typ`, deterministic sha test); 5 attest tools with argument lists, one `policy.attestation_bar` setting, `export_cpa_package` `without_audit_reason`; 6 attention, handbook `cpa-handoff.md`, For-P12. Size 10-12 days.
## P9 / P10 / P11
- **CD-G1.** Dispatched commands (`ledger-cli/src/main.rs`): `gui, setup, server, host, mcp, serve, close, books, doctor, token, connect, passphrase, upload, download`; `ledger-host/src/commands.rs`: `serve, close, books (new_books, adopt, put_aside, restore/plan_restore/carry_out_restore, upgrade/upgrade_format, back_up_now), connect/connect_agent, token, passphrase, doctor, upload, download`. No `update`, `helper`, `tunnel`, `fetch`. P9's move = `commands::adopt` + `admin::Request::Adopt`/`AdoptPreview`.
- **CD-G2.** `connect_agent(common, agent, book)`: finds `claude`/`codex`, makes the token (`make_token`, `tokens::keeper(..).set(..)`), runs `<cli> mcp remove|add wanderbooks-<book> -- <this_executable()> mcp --book <id>`; writes nothing into a project folder (Codex global). The books home is new: `commands::books_home(book) -> PathBuf` + files, `connect_agent` unchanged.
- **CD-G3.** Viewer menu: `menu.rs` `Menu`/`Job::{Package, Worksheet(y)}`; `owner.rs` `trait Owner { close_books, new_books, back_up_now, plan_restore, restore, connect(Agent, book), upgrade_format(book, passphrase) }`, `Dialog::{NewBooks, Restore, Close, Upgrade}`, implemented over the admin socket (`ledger_mcp::admin::{Request, Response, call}`), not "the lease socket's owner intents" (plan 857 stale). The dock = new `Owner::start_agent(Agent, book) -> Child` + a panel in `app.rs`.
- **CD-G4.** Built already (P11 lists as to-build): `host.rs` INVARIANT 7 `run/update.lock` (`take_update_lock`), `Ending::Restart`, `restart()`, the version handshake refusal; `bridge.rs` `with_list_changed`, tests `the_agent_hears_of_changed_tools_only_after_a_restart`, `bridge_survives_server_restart`, `bridge_initializes_during_cold_start`. The updater = fetch, verify, stage, swap, then the existing restart.
- **CD-G5.** Backup/restore reuse: `ledger-store/src/backup.rs` (`Git`, `commit`, `push(Target)`, `verify`, `drill`, `prove_archive`), `ledger-mcp/src/backup.rs` (`Backups::{open, standing, drill_due, drill}`, `Upgraded`, `UpgradeBackup`), admin `Request::UpgradeFormat`, `BackUpNow`, `Restore`/`RestorePreview`; `Store::upgrade_format() -> Option<FormatUpgrade>`. Routes in `local.rs`: `/b/{book}/mcp`, `/u/{upload}`, `/a/{attachment}`, `/x/{export}`; `bearer(headers)` → `auth::Token`, `Scope { Read, Write, Post, Admin }`.
- **IR-P9-1.** P9a (no decision needed): `deploy/wanderbooks-server.service`, `deploy/README.md`, `self_host_adopt_same_head`, `.github/workflows/two-user.yml` running `agent_uid_cannot_read_tenant` (neither exists today), static bearer over a documented Tailscale Funnel; no seccomp/Landlock. 3-4 days. P9b after D-7. P9c not planned. External: a machine, tunnel, domain/TLS, a reviewer.
- **IR-P10-1.** Not an engineering unit: `docs/runbooks/re-audit.md` against existing tools plus a CI test `defect_reproductions_all_fixed` asserting the 24 named regression tests exist. 1 engineer-day; owner-run after P5, P6, P8.
- **IR-P11-1/2.** Non-code prerequisites listed separately (Apple Developer ID and notarization credentials as CI secrets; Windows signing identity; GitHub Releases on the mirror; the release hardware token; clean VMs). Order: 1 trial (2-3 days, output a change-log entry per format); 2 `wanderbooks update`, `rollback`, staged layout `<root>/updates/<version>/`, tested against a `file://` feed (`update_is_atomic`, `failed_update_rolls_back`, `unsigned_update_refused`, `bridge_survives_update`); 3 Linux packages in CI containers (unsigned first; `linux_packages_install_and_upgrade`); 4 signing (minisign + OS; the two-key manifest per the owner's answer to Q-4); 5 macOS/Windows bundles; 6 dock as its own unit (`alacritty_terminal`, books home contents verbatim, `dock_runs_the_chosen_agent`, `books_home_holds_no_books_data`). Size 20-25 (1-5) + 8-10 (dock).
## P12 / P13
- **CD-H1.** Nine fuzz targets (`fuzz_config_parse, fuzz_core_serde, fuzz_entry_validate, fuzz_import_csv, fuzz_journal_line, fuzz_op_sequence, fuzz_settings_replay, fuzz_store_open, fuzz_store_replay`), scheduled.yml at 600 s each; `fuzz_op_sequence::Op` has no document op. Mutation recipes: `mutants` (core; excludes proofs.rs; `-j 4`), `mutants-store` (`STORE_CASES=64`, `-j 3`), `mutants-import`, `mutants-config`, `mutants-mcp` (`MCP_CASES=16`, with ledger-cli tests, `--no-default-features`); CI shards core 8 / store 4 / import 2 / config 1 / mcp 16, `timeout-minutes: 350`. No recipe for `ledger-host`, `ledger-package`, `ledger-vault`, `ledger-cli`: P12 adds them. `--jobserver-tasks` is not used (the recipes use `-j`). Pins: cargo-mutants 27.1.0, cargo-fuzz 0.13.2, kani-verifier 0.68.0, lean4 4.34.1.
- **IR-P12-1.** Parameters: 4 h per fuzz target (or the owner's number); the maxima are `STORE_CASES`, `WB_ORDERS_DEPTH=6`, proptest case counts named in justfile/docs/testing.md; build `fuzz_http_mcp_request`, `fuzz_upload_put` first (2 days); CC-2's sections first. Size 2-3 engineer-days of code plus machine time.
- **CD-I1 Blocker.** The in-repo seed is the starting point: `bookspec/README.md` (schema: `id, title, rule, status, sources, tags, appendix?`; `[setup]` fields; `[[events]]` kinds `lines | reverses | close | bill | invoice | run_depreciation | dispose_asset | run_schedules | register_schedule` plus `refused, payee, parties, instrument, older`; `[[changes]]`; `[[expect]]` with ~30 queries); 56 examples; loader `crates/ledger-core/tests/bookspec.rs` (`#[serde(deny_unknown_fields)]` structs, `every_bookspec_example_holds`, applies events to a real `Store` and `verify`s); `ledger-package/tests/package.rs::the_bookspec_iif_example_matches_the_engine`. The `Engine` trait is today the loader over `Store`.
- **CD-I2.** P4b's bookspec ids need loader extensions: `InvoiceSpec`/`BillSpec` lack `due_date`/`terms`; no `receive_payment`/`pay_bill`/`void_document`/`write_off` events; no `ar_aging`/`ap_aging`/`control_check`/`document` queries; the refusal vocabulary needs `UntaggedControlLine`.
- **IR-P13-1 order.** 1 move the loader into `bookspec/checker/` (own Cargo workspace) exposing `Setup`, `Event`, `Expect`; `ledger-core/tests/bookspec.rs` becomes the adapter by path dependency; 2 `pub trait Engine { fn load(&mut self, &Setup) -> Result<(), Refusal>; fn apply(&mut self, &Event) -> Result<(), Refusal>; fn query(&self, &Query) -> Result<Value, Refusal>; fn claims(&self) -> Claims; }`, `Refusal { rule: RuleId, reason }`; 3 rule ids `BS-<area>-<n>` mapped from B-1..B-41; 4 the spec skeleton with a done line ("every B-rule has an id, statement, source, one example; the five laws are Lean theorems in `bookspec/lean/`"); 5 runner (pass/fail/not claimed), GnuCash adapter later. In-tree until step 5. Size 15-20 days.
## Cross-cutting reuse to cite (CD-J)
`crates/ledger-cli/tests/common/mod.rs`: `Home` (`new`, `create`, `adopt`, `token`, `token_with`, `admin(Request)`), `Server::start`, `Wire` (`initialize`, `call`, `rpc`, `upload`, `begin_and_send`, `fetch`, `fetch_text`), `Books` (`new`, `with`, `placed`, `adopted`, `wire`, `session`, `settle`), `upload_args`, `answer_of`; parity precedent `projects_parity.rs`; older-rules fixtures `store.rs::books_written_under_older_rules_still_open`, `arap_standing.rs::legacy_documents_replay_with_tags` (exists; plan lists it as to-build); `BooksServer::as_of(Option<&str>)` the one date source; `BooksServer::hand_over(book, file_name, mime, bytes) -> HandedFile`; `parse_doc_ref("invoice:7")`, `doc_ref_text`, `doc_label`, `movement_key` (an "INV-0007" form is a new parser arm).
## Summary table
| Unit | Depends on | Non-code prerequisites | Size (eng-days) |
|---|---|---|---|
| Pre-steps (recipes, testing.md S1B/S2/S3, attention over PackageBooks, server.rs split) | — | — | 4-6 (+ the 700-line attention refactor) |
| S4 | S1-S3 | go-ahead for live use only | 12-16 (4 PRs) |
| S5 | S1 | same | 3-4 |
| S6 | S4 step 1 | same | 8-10 |
| S7 (cut) | S4 step 6 | same | 4-6 |
| P4 (cut) | S4-S7 MCP steps | shared go-ahead | 8-10 |
| P5 | P4; envelope single-owner | live-session check | 10-12 |
| P6 | — | — | 8-10 |
| P8 | P6 step 3, P4 | — | 10-12 |
| P9a / P9b | — / D-7 | machine, tunnel, TLS / reviewer | 3-4 / later |
| P10 | P5, P6, P8 | owner-run | 1 |
| P11a / P11b dock | — | certificates, keys, VMs | 20-25 / 8-10 |
| P12 | everything | desktop | 2-3 + runs |
| P13 | — | — | 15-20 |
@@ -0,0 +1,41 @@
# 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.
+83
View File
@@ -0,0 +1,83 @@
# Stage 5: Zoom-out review of `plan/served.md` (full file; focus P4b S4 – P13)
Two independent `egm-zoom-out` reviewers (A, B), 2026-10-26. Findings deduplicated; where both reported the same point it is marked (A+B). Line numbers are the file as it stood at `c1f9001c`.
## Blockers
### Z-1 (A+B). Section 8 has no "where we are / what is next / what runs in parallel" map
Scope: §8 preamble 766-774; P4b "Status" 997; P4 header 971.
Preamble still says every phase "runs mutation testing" (D-33 paused it). Line 997 says "slice S1 next" while S1-S3 are on main. P4 has no status line. P4b.3 calls `stands_on` "new code" though it is built (`ledger.rs:2106`). A fresh engineer cannot tell which of P4 and S4 is first. Fix: a status/dependency/ownership table at the top of §8; correct 997; "as built" blocks for S1-S3.
### Z-2 (A+B). P7 "Vault v2 and the human channel" builds what D-28, D-29, D-31, D-32 forbade
Scope: P7 1697-1723; 5.6 row 644; 4.9 509, 535; 4.1 370; risk 5 1907-1908; P10 step 6 1787.
`/h/tin`, `/h/w9`, `/h/unlock`, `request_tin_entry`, `set_w9_facts`, removal of `set_tin`/`set_w9`/`include_tins`, GUI passphrase unlock, HPKE keyslots, recovery key, `rekey`. D-28: TINs go through the agent into the vault, "no human web pages". D-29: TIN exports keep working for the agent, "no passphrase, no extra prompts". 3.6 as-built already says so. The live code agrees (those tools exist in `server.rs`; `/h/` appears once in the crate tree). Fix: shrink P7 to the known TIN-scanner defect (line 200: "123,45,6789") and a decision record closing vault v2; strike the superseded sentences elsewhere.
### Z-3 (A+B). P4 is unreconciled with the built P4b and the two reference each other in both directions
Scope: P4 971-993; P4b.3 1109; P4b.5 1236; P4b.8 1285, 1291; P4b.11 1291.
(a) P4 makes reversals drafts needing evidence; S1 built `books.void_document` as an immediate reversal. The plan never says which wins or what evidence a void needs. (b) S4 "takes `preview_token` once P4 lands" while P4 is listed after P4b; `preview_token` exists nowhere in the code. (c) P4 adds four new event types but names no format feature and no owner upgrade (D-39); decision 11 batches P4b's upgrades after S1 and S7, so S4-S6 writes are unusable on the live books until the S7 upgrade, which the plan never says out loud. (d) Every code pointer in P4 is stale (see Z-11). Fix: one order written down (recommended: S4-S7 with `confirmed` only, then P4 retrofits `preview_token` in one sweep); P4 states a void is a reversal with evidence inherited from the original (D-12 generalized); P4 names a feature and joins S7's upgrade batch.
## Major
### Z-4 (A+B). The attention engine: new crate `ledger-view` is YAGNI and arrives too late
Scope: 2.1 line 96; P1 as-built 832; P4 979; P4b.8 1330; P5 1640-1649; P8 1744.
No `ledger-view` exists. `attention::find` is in `ledger-gui/src/attention.rs:109` with helpers in `books.rs` and `pages/parties.rs`; the server's `next_steps` are hand-coded in `ledger-mcp/src/tools/kept.rs:764-832`. S4-S7, P4 and P8 each add attention items, so every item is written twice until the move. `ledger_package::PackageBooks` is already the shared read-only view over Store and Snapshot. Fix: move attention over `PackageBooks` (in `ledger-package` or `ledger-store`) as the first unit of remaining work; no new crate or trait.
### Z-5 (A+B). P6: `ledger-extract` sandboxed subprocess contradicts D-32 and adds a third PDF parser
Scope: P6 1664-1672, 1689; 3.1 128-129, 139; 2.1 94.
PDF text and page rasters already exist with hayro in `ledger-gui/src/receipts.rs` (`text_of` :467, raster :390) and `ledger-mcp/src/tin_files.rs:314`. Fix: one in-process `text_of`/`page_png` in `ledger-import`, called by server and viewer, journaling `attachment.text_extracted`; drop the subprocess, sandbox test and lopdf. Also stale: `inline_base64_requires_sha` (D-46), "the `company/` folder retired" (done), `attach_email` (unrequested; `.eml` is not an upload purpose).
### Z-6 (A+B). P6 migration tools and P10 step 5 build engine code for a one-time comparison whose inputs are private
Scope: P6 1682, 1695; 5.6 643; 4.9 507; P10 1786.
`compare_reference`, `record_inventory`, `check_inventory`, an exact-decimal XLSX reader, and journal events `inventory.*`/`reference.compared` replace scripts that now live only in the owner's private `legacy/`. The Sheet and the books as of 2026-10-11 are frozen; the final `verify-sheet` run is a filed workpaper. Fix: P10 step 5 cites the filed workpaper and records a decision; drop the tools. Dedup (defect 4) is partly done already (`attach_receipt` returns `deduplicated`).
### Z-7 (A+B). P8: `books.audit` re-implements existing checks; the attestation-bar machinery is heavy for a self-reported claim
Scope: P8 1724-1752; D-9 1949; risk 11 1930.
Integrity = `Store::verify`/`verify_blobs` + access-log chain; tie-out = `control_check` (already in `verify` and the digest); cash flow `reconciles` exists. Fix: compose `books.audit` from existing checks plus the genuinely new ones (closes in order with member deltas; schedules posted through), writing a workpaper (needs a `workpaper` document category: store categories today are receipts, formation, legal, tax, accounting, other). Attestations record who/when/what was claimed; the bar is a handbook convention; the CPA package warns, or refuses with an override per D-9. Drop `policy.attestation_bar` versioning and the Lean counting lemma. "Run as a job" depends on `books.job` (Z-9).
### Z-8 (A+B). P9 is three products in one phase, not on the P10 path, and still prescribes seccomp/Landlock units
Scope: P9 1754-1777; risk 8 1919; D-7 1988; 4.7 470.
In-house OAuth 2.1 server (security-critical, needs external review), replica viewer protocol, passkey invitations, MCP App widget, `openai/fileParams`. P10 already allows the claude.ai leg "not applicable". Fix: P9a = self-host recipe + `books adopt` with head check + static bearer over a tunnel; P9b = claude.ai via D-7(b) external IdP only when wanted; P9c = replica/invites not planned. Remove seccomp/Landlock from line 1759 (D-32).
### Z-9 (A+B). `books.job` and the 20 s rule are premature locally
Scope: 3.2 153; P5 1645; P8 1726; P9 1776.
No measured call approaches a client limit over the bridge. Build jobs in P9 where claude.ai's 240 s limit is real.
### Z-10 (A+B). P11 builds the dock on the helper and confinement D-32 removed, and fuses packaging with the dock
Scope: P11 1798-1833, esp. 1817, 1827, 1801-1812.
No `helper.rs` exists; 3.9 as-built says "No confinement, no helper". The dock spawns the agent as an ordinary child. Split: P11a packaging/updaters (Linux first; the format gate is already specified in 3.9), P11b dock + macOS/Windows signing. Within P11a the bespoke two-key manifest wraps cargo-packager's minisign: start with minisign + OS code signing (meets D-24); add the manifest only on an accepted threat.
### Z-11 (A+B). Stale line numbers, schema numbers and symbol names throughout the remaining phases
Scope: P4 974-976 (`ledger.rs:1355-1380`, `:1319`, `S:561-568`, `S:2540`, `receipt_policy.rs:6`, `books.rs:648`, "schema v12"); 5.2 588; P5 1648; risk 13 1932 (server.rs "8,529 lines": now 12,566).
`check_reversal` is at `ledger.rs:1988`, `reverse_entry_steps` at :2170; the gate is `ledger_config::ReceiptPolicy` via `ledger-mcp/src/policy.rs`, not `ledger_core::evidence::requirement`; `SCHEMA_VERSION` is 15 (shipped 14 per STATUS). Fix: symbol names and files, never line numbers.
### Z-12 (A+B). P10's steps and exit criteria name mechanisms that were never built or were superseded
Scope: P10 1782-1796.
`wanderbooks fetch`, `verify-archive --expect-head` (D-31: restore preview and drills instead); "TINs through the human link" (D-29: `wanderbooks download`); step 5 needs Z-6's tools; step 2 needs Z-7's bar machinery. Fix: restate P10 against what exists: `books.verify` head, backup drill, `books.control_check`, the 22 defect reproductions, the CPA package from both doors.
### Z-13 (A). `server.rs` is the merge hotspot for a parallel team and the promised split is not happening
Scope: risk 13 1932; 4.9 542.
12,566 lines; six files under `tools/`. Every remaining phase adds tools there and to `types.rs`, `scopes.rs`, `docs/mcp-interface.md`, `docs/testing.md`, `docs/spec/rules.md`. Fix: a single-owner pre-step splitting `server.rs` by tool group before fanning out; each unit states which files it owns.
### Z-14 (A+B). P4b carries two designs for the same thing and no "as built" markers
Scope: 995-1636.
Owner answer 3 (1002) specifies `invoicing.payment_methods` list with QR; P4b.4 (1215) and P4b.11 (1367) still say a single `invoicing.payment_instructions` string. `qrcode` with `svg` is already a dependency of `ledger-import`. Answer 4's processor fee (gross to A/R, fee to expense, net deposited) appears in no tool argument or slice; STATUS puts it in S6. S6 and S7 partly exist (`Movement::Discount`/`WriteOff` and their derivations shipped in S1). Fix: pick the list; put the fee in S6; "as built" for S1-S3; "to build" for S4-S7 scoped against what exists.
### Z-15 (A+B). Background sections a fresh reader would wrongly build
Scope: 4.1 370; 4.9 504-509, 535; 4.7 467, 476; 5.2 577-584; §6 663, 685-697; risks 1, 6, 12; 7.4 738; 7.6 751.
TIN human pages and tools, `books.inbox`, `books.attach_email`, `books.download_link`, `books.invite`, "Removed: `get_w9`", `/h/inbox`, P6/P7/P8 event rows, Landlock/seccomp, `ledger-view`, "small receipt files go straight through the chat". Fix: strike-through with the superseding decision, as 2.1's inline-bytes row already does.
## Nits
- Z-16 (A). P4b.8/P4b.9 weeds: mailto body cap, `party_id` alias "for one release", paper size, reminder day counts belong in slice briefs or GOAL blocks.
- Z-17 (A+B). P13: engine adapters for hledger/Beancount/QuickBooks are speculative; the move to its own repository must say how `just check` keeps running the examples (vendored copy or pinned dependency); keep in-tree until the harness is stable.
- Z-18 (A). P6 `reimbursed` derived by FIFO is a heuristic that will be wrong; `entry.settles` plus an attention item is simpler.
- Z-19 (B). `preview_token` as an HMAC with a key file buys little locally (I-6 admits it); a plain `preview_digest` detects staleness. Say whether forcing `preview_entry` is wanted.
- Z-20 (B). P12 says "every fuzz target, the old and the new": `fuzz_http_mcp_request` and `fuzz_upload_put` were never added. List what P12 must build versus run.
## Forced dependency order (reviewer B's synthesis, checked against A)
1. Pre-step: attention engine over `PackageBooks`; `server.rs` split by tool group (single owner).
2. P4b S4 → S6 (S6 needs `Terms`, `discount_by`, `max_discount`). S5 and S7 need only S1 and can run beside S4.
3. P4 after S4-S7 (or S4 without `preview_token`); P4 and S7 share one owner upgrade.
4. P5 after P4 (`books.start` v2 lists drafts by state).
5. P6 (extraction only) before P8's receipt cross-check and P10 step 2.
6. P5, P6, P8 before P10. P12 last, over everything.
Independent of the P10 path: P9 (whole), P11, P13's spec writing, the P7 scanner fix.
+4 -1
View File
@@ -23,7 +23,10 @@ The primary interface is MCP, not a cloned GUI (the owner's call, 2026-10-10).
- Parallel run with the sheet; cutover per the cutover rule
(`docs/parallel-run.md`).
## Served (next, 2026-10-16)
## Served (2026-10-16; reworked for a team 2026-10-26)
The remaining work, its order, its dependencies and its hand-off are in
`plan/served.md` section 8.0; the team's prompt is `plan/HANDOFF.md`.
The books become a server that owns its data; every agent reaches them
only through the MCP, from anywhere; no scaffold. Phases P0 (guard
+961 -942
View File
File diff suppressed because it is too large. Load diff