fix(mcp): a read says only what follows; a success adds no empty warnings (P5)

Two existing contracts the envelope broke when every result got every field:
a resource and its tool say the same (books://settings is books.settings,
checked by the_kept_resources_read), and a tool with no warnings has no
warnings key (books.add_account, defect 8). So head and next_steps are on
every tool that can change the books, next_steps is on a read only when
something follows it, guards is on a refusal, and the envelope adds no
warnings of its own (a tool's stay as they are).

The happy-path spec test now exercises books.journal_history (it fails on
a tool it never called), the demo company gets its books.start size check
(Done when 3), and the As-built block of P5 is written.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HS8WZziDnyxunC23MUxrLp
This commit is contained in:
swaitsandClaude Fable 5.1 committed 2026-10-29 15:48:42 -06:00
1 parent 8e683942a2
commit ad1afb478d
6 files changed
+76 -11

No files matched your search

+9 -1
View File
@@ -1515,7 +1515,15 @@ Each unit follows the 8.0.4 template. "A-n" is an owner answer numbered in 8.3.1
5. **Files:** owns `crates/ledger-mcp/src/guards.rs`, `tools/prompts.rs`, `prompts/*.md`, `crates/ledger-store/src/describe.rs`, `crates/ledger-cli/tests/agent_evals/`; touches `server.rs` (the result path), `tools/kept.rs`, `opening.rs`, `bridge.rs`, `handbook.rs`, the 8.0.3 files, `docs/mcp-interface.md`.
6. **Tests and proofs:** named above; no kernel or Lean change.
7. **Done when:** (1) every mutating result carries `head` and non-empty `next_steps`; (2) every guard's remedy tool exists; (3) `books.start` ≤ 8 KiB on the `p2-served` fixture and on the demo books; (4) the prompts list and render; (5) `tool_descriptions_fit_700_characters` pins 0; (6) the six evals pass; (7) `just check` green. The live check (a fresh Claude Code session in an empty folder, and a Codex session, each posting an entry by following `next_steps`) is the EM's, recorded in the As-built block.
8. **As built:** none yet.
8. **As built (2026-10-29, branch P5, eight commits on b97234ac; no schema change, so no migration number was taken):**
- Step 1 as written, with these shapes: `next_steps` items are `{ tool | prompt | command, args?, why, required }` (the `command` form carries `books.export_cpa_package`'s download command, which is not a tool); a result that already has `next_steps` or `head` keeps it (`books.start` has its own `next_steps` and `head`, `books.verify` and `books.access_log` their own `head`); the envelope adds no `warnings` of its own (a tool's own stay as they are, `books.cash_flow`'s object warnings included) and `guards` is on a refusal only; `head` and `next_steps` are on every tool that is not read-only (its annotation), `head` being the journal's head after the call; a read has `next_steps` only when something follows it, because a resource and its tool must say the same (`the_kept_resources_read` compares `books://settings` with `books.settings`), and an empty `warnings` on `books.add_account` broke `add_account_warns_without_tax_category_defect_8`. A refusal that is a guard carries `structuredContent.guards`; its text is unchanged. After a close the step is `books.verify`; **P8 replaces it with `attest_period` when it merges**. The guard registry has ten codes (the plan's seven examples plus `party_shares`, `party_required`, `open_drafts`); `untagged_control_line`, `duplicate_vendor_ref` and `first_number_locked` are in the registry and recognized from a refusal that begins with the code, but no tool refuses with them yet. The Needs-attention items are added after a change to an entry or an invoice only (not to a party or an account). `every_guard_names_an_existing_tool` is in `guards.rs`, `every_mutating_result_has_head_and_next_steps` in `server_tests.rs`.
- Step 2 as written, except: `head.last_handoff`, `rules.attestation_bar`, `rules.approval_rules` as a list and `attestation` are not in v2 (P8's); `you.verified`/`self_reported` and `isolation`'s shape were not added to `you` (the plan's `you` has four keys and has them). `agent` is stored on the access record as `agent {model?, reasoning_effort?, harness?}` (a new optional field of `ledger_store::access::Access`; the access log is its own chain, not the journal, so 8.0.6 does not apply). `you` is absent on the `books://start` resource (no token made that call). The fit loop shortens attention items to four and waiting drafts to three before the lists.
- Step 3 as written. The prompt tests are in `crates/ledger-cli/tests/agent_surface.rs` (`the_prompts_list_and_render`), not `crates/ledger-mcp/tests/prompts.rs`: the prompt renderer is `pub(crate)` and the p2-served harness is in ledger-cli; the "golden render" is each prompt's file with its blanks filled, compared exactly, rather than a stored copy. Prompts need `books:read`. The report resources now take their dates from the query; `books://entries/{id}` and `books://journal/{seq}` are new reads; `books://attachments/{id}` was already served.
- Step 4: `books.journal_history` as written, in `tools/journal.rs`, `subject` being the line's first subject. **Departure:** the describer in `crates/ledger-store/src/describe.rs (new)` is the server's; the viewer's `describe` arms in `ledger-gui/src/pages/journal.rs` were not moved (they carry icons, routes and today's party and account names; moving them behind a formatting trait was a large change to the viewer for no figure or behavior). So there are two describers; the server's quotes only identifiers, dates, amounts and short typed names and never a setting's value. Decide with the EM whether to unify them (U-unit).
- Step 5: parameter names are `reason` (on `books.set_convention`) and `revises` with `reason` (on `books.record_decision` and `books.raise_item`); `subject`, `decision_md` and `title` became optional in those two schemas so a source-only call can leave them out (given, they must equal the record's). Test names: `a_source_only_revision_is_journaled`, `source_revisions_wait_for_the_upgrade` (both in `crates/ledger-store/tests/source_revisions.rs`), plus `a_source_revision_must_change_only_the_source` and the over-the-wire `a_source_only_revision_over_the_wire`. 8.0.6: all six items (the `p5_before` fixture was written by b97234ac; no golden moved).
- Step 6: the eight descriptions were moved into the handbook sections `cpa-handoff`, `parties-and-tins`, `1099`, `files-in-out`, `reconciliation` and `viewer`; `tool_descriptions_fit_700_characters` pins none and the allowance list is gone. `instructions_budget` was already green with the final connect text (it already had no `preview_token`, reversal drafts or `request_tin_entry`). The aliases are removed (and `customer`/`vendor` are required); every over-the-wire test that used them was changed to the new names (`party_id_alias_still_works` became `a_customer_by_id_and_by_name_is_one_record`). `docs/mcp-interface.md`'s overview sections now describe the surface as built.
- Step 7: the six transcripts are as named. Each file may have `setup` (direct calls that make the chart and the books an agent finds; not the agent's moves), `settings`, and steps `call` (only `books.start`, first), `follow`, `follow_prompt`, `expect_next_steps_contains` and `expect`; `{"$upload": ..}` and `{"$var": ..}` stand for an uploaded file's id and a value a setup call saved. The runner is `agent_evals.rs`.
- Not run by the worker (D-60): `just check`, `just prove`; the live check of Done-when (7) (a fresh Claude Code session in an empty folder and a Codex session, each posting an entry by following `next_steps`) is the EM's. Checked: `cargo test -p ledger-mcp`, the store tests `source_revisions`, `format` and `store`, and the over-the-wire files this unit edited, with fmt and clippy on the five crates touched.
**Struck from P5:** ~~`books.job` and the 20 s rule~~ → D-53 (no measured call approaches a client limit over the bridge; jobs come with P9b if claude.ai's 240 s limit is ever real); ~~URL-mode elicitation~~ → nothing local to elicit (D-28); ~~`ledger-view` crate~~ → U0b (`PackageBooks`); ~~`books.viewer_status`~~ → `books.show` already reports `viewer_open`.