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>
This commit is contained in:
co-authored by
Claude Fable 5.1
Jakob Wennberg
parent
f767977bf5
commit
67e878fb23
@@ -1511,6 +1511,7 @@ One line per decision: `[YYYY-MM-DD] <decision>: <why>`. Appended by agents and
|
||||
[2026-09-02] Agent-triggerable bank sync shipped (v1 POST /bank-connections/{id}/sync + MCP gnubok_sync_bank), lifting the 2026-09-01 deferral: Emil chose to close every open F2 item in one PR. The cost worry is bounded structurally instead of by policy: the window is never caller-controlled (gap-aware 7 to 90 days, same helper as the cron), a connection synced within 15 minutes answers BANK_SYNC_COOLDOWN with next_allowed_at (MCP returns it in-band as synced=false so agents read on instead of retrying), and failures are throttled per process by attempt time. The web Synka-nu route is left untouched rather than refactored onto the shared runner: it carries UI-only behaviour (caller-chosen days_back up to 365, SIE sweep stamping) and a regression there would hit every user for a code-sharing win.
|
||||
[2026-09-02] Bank-sync cooldown is a durable lease column (bank_connections.sync_lease_until, migration 20260902150000) claimed with one conditional UPDATE, not a process-local attempt map: the security scan on PR #2165 showed the map is bypassed by a second serverless instance or a cold start, so two agent calls could each bill Enable Banking. A column add was chosen over reusing extension_data because PostgREST cannot express an atomic conditional upsert there; the nightly cron deliberately ignores the lease.
|
||||
[2026-09-02] Grok links carry auth=required like the claude.ai link (#2159), decided from a live test: on the lazy URL Grok's connector dialog listed all 150+ tools and never opened the sign-in, so it reads the 200 probe as an authless server exactly as claude.ai does. The flag lives in one helper (mcpServerUrl / sideDoorServerUrl in lib/onboarding/checklist.ts) so the settings row, the onboarding side door and the deep link cannot drift; ChatGPT stays lazy because its developer mode honours the 401 on the first protected call.
|
||||
[2026-09-02] reconcile_match staging folds N:1 groups back together by journal_entry_id (links from the dry run that share a verifikat and carry no allocated_amount), mirroring the existing bank 1:N fold, instead of threading the caller's original pairs through: the dry run is the source of truth for what was validated, a verifikat can only be settled once so shared-JE links can only have come from one N:1 pair, and use_proposals has no caller pairs to thread. The remaining gap (dry run validates only pair shape for non-split pairs) is the engine's to close, not the tool's.
|
||||
[2026-09-02] parties children/roles reference parties(id, company_id) with composite FKs, not parties(id): a party UUID from another tenant is rejected by construction instead of relying on each writer to check; ON DELETE SET NULL (party_id) on customers/suppliers because a plain SET NULL would null company_id too (Superagent P2 on #2162)
|
||||
[2026-09-02] Party suggestions attach only by explicit party_id, org number or an exact ledger key already in alias_keys; same-core text is reported as similar_to for a person to decide and identities are withheld when a key mixes org numbers: the selection eval measured 9% false merges on trade names shared by distinct legal entities (Fortnox AB / Fortnox Finans), so text never merges
|
||||
[2026-09-02] Edited migration 20260902160000 after merge: its backfill failed on prod (ensure_party: name is required; 3 nameless rows) so it was never applied there, the Supabase main branch sat in MIGRATIONS_FAILED and every later migration was blocked behind it. An unapplied file is not a shipped schema; a follow-up migration could not run before it
|
||||
|
||||
Reference in New Issue
Block a user