Stop the vault tools promising verification they don't perform

Three findings from the automated review passes, all confirmed against the
code before changing anything.

The download URL was dead on arrival for the caller it was built for. Sure
serves stored files through Active Storage controllers that
config/initializers/active_storage_authorization.rb gates on
`viewable_by?(Current.user)` — a signed-in browser session. An MCP client has
a bearer token and no session, so following the URL would have redirected to
sign-in. Removed it rather than leaving a link that cannot work, and the
description now points at search_family_files or the vault UI.

Coverage called a month `covered` when a document merely existed. An
unreconciled statement is not mismatched, so it took the `covered` branch, and
the payload carried nothing to correct the reading — the same "advertised
verification that never happened" bug fixed last round in
get_account_statement, in a second place. Months now carry their own
reconciliation_status, and the description says covered means presence, not
agreement.

Listing filtered visibility after limiting. Beyond underfilling a page, with
no cursor and a 100-row cap an accessible statement behind enough newer
invisible ones was unreachable. Visibility now lives in the query, mirroring
viewable_by? for a statement manager.

Also: rescue unexpected upload failures into a tool error instead of a raw
exception string, derive the documented size limit from MAX_FILE_SIZE, list
every coverage status in mcp.md, and cover the failed-reconciliation and
base64-normalisation branches.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JFDp9HhXDeswadu4cxFojn
This commit is contained in:
Claude
2026-08-01 20:10:34 +00:00
parent 3d5e9efa01
commit 89a2f53126
9 changed files with 122 additions and 54 deletions

View File

@@ -15,13 +15,22 @@ class Assistant::Function::GetStatementCoverage < Assistant::Function
Each month comes back with one status:
- `covered` a linked statement covers the month and reconciles
- `covered` a linked statement covers the month. This means a DOCUMENT
EXISTS, not that it agrees with the ledger: most statements have no
balances entered, so there is nothing to reconcile and they still count
as covered. Check `reconciliation_status` on the month before saying a
month is verified.
- `mismatched` a statement covers it, but its balances disagree with the ledger
- `missing` no statement on record; the month's figures have no document behind them
- `ambiguous` — a statement was suggested for this account but nobody has confirmed the link
- `duplicate` — two or more linked statements overlap the same month
- `not_expected` — outside the account's expected statement range
Each covered month also carries `reconciliation_status`: `matched` when
every statement in it reconciles against the ledger, `mismatched` when one
disagrees, and `unavailable` when nobody has entered the balances which
is the common case.
Use it before asserting anything about a period: "no statement on record"
is a legitimate and necessary answer, and is very different from "the
balance was zero". Use it to tell the user exactly which documents to go
@@ -87,8 +96,22 @@ class Assistant::Function::GetStatementCoverage < Assistant::Function
{
month: month.date.strftime("%Y-%m"),
status: month.status,
# A month is "covered" on document presence alone — an unreconciled
# statement is not mismatched, so it lands in `covered`. Without this
# field an agent cannot tell "the ledger agrees" from "nobody checked".
reconciliation_status: reconciliation_status_for(month),
statement_ids: month.statements.map(&:id),
unconfirmed_statement_ids: month.ambiguous_statements.map(&:id)
}.compact_blank
end
def reconciliation_status_for(month)
return nil if month.statements.empty?
statuses = month.statements.map(&:reconciliation_status).uniq
return "mismatched" if statuses.include?("mismatched")
return "unavailable" if statuses.include?("unavailable")
"matched"
end
end