Files
accounted/extensions
Jakob Wennberg 67e878fb23 fix(mcp): reject unparseable voucher lines and allocation kinds; already-booked and unlinkable say why; N:1 reconcile groups survive staging (#2171)
* fix(mcp): reject unparseable voucher lines and allocation kinds; already-booked and unlinkable say why; N:1 reconcile groups survive staging

Four MCP feedback reports about the same failure class: the server does no
runtime validation of inputSchema, so a shape mistake was coerced into a
wrong-but-well-formed call, and the error the agent finally saw pointed at
the wrong thing.

- create_voucher / correct_entry: a line naming neither debit_amount nor
  credit_amount was `Number(undefined) || 0`-ed into 0/0, and the balance
  check reported "debits 0 SEK, credits 0 SEK" for four perfectly balanced
  formats ({debit}, {amount, side}, {debitAmount}, signed amount). The line
  shape is now checked first, the error names the keys it got and shows a
  valid line, and a non-numeric amount is rejected as such (seq 318571).
- match_batch_allocate: kind is the key every guard branches on (direction
  vs sign, required id per kind, tenant pre-check on the invoices). With
  kind absent none of them fired: an incoming +50 359 SEK payment against
  three kundfakturor staged as allocations_kind "supplier_invoice" with zero
  invoice checks. A missing or unknown kind is now rejected before any
  query, with the id field that goes with each kind (seq 319919).
- categorize_transaction on an already-booked transaction returned the
  core's success-shaped object, which fails STAGED_OPERATION_SCHEMA on
  strict clients: the agent saw "Structured content does not match the
  tool's output schema" and never the reason. It now throws, naming the
  existing journal_entry_id (seq 288574).
- reconcile_match: the dry run flattens a pair into one link per outside
  row, and the staging rebuild put each back as its own 1:1 pair, so an N:1
  group (Skatteverket "Avdragen skatt" + "Arbetsgivaravgift" against one
  1630 verifikat, sum exact) reached the executor as N pairs each asked to
  settle the whole verifikat: PAIR_NOT_CLOSED on all 18. Links sharing a
  verifikat now fold back into one pair, mirroring the existing 1:N fold,
  and "No linkable pairs" carries the dry run's skip reasons (seq 292682).

No tools/list payload change: no schema text touched.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013yw62FMXGSzo6icFDiBwP3

* docs(decisions): N:1 reconcile fold at staging

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013yw62FMXGSzo6icFDiBwP3

---------

Co-authored-by: Jakob Wennberg <311770904+jakobwennberg-oss@users.noreply.github.com>
Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-04 09:27:26 +02:00
..