---
name: transaction-audit
description: Read-only quality check on a TransactionDraft. Reconciles totals, flags duplicates, validates merchant + project assignments. Critic role for the receipt-processing workflow's attribute step.
allowed-tools: query_finance query_knowledge
title: Transaction Audit
category: finance
status: active
metadata:
  triggers:
  - audit transaction
  - review draft transaction
  - check this receipt
  priority: 5
  model_tier: capable
  language:
    output_policy: source
---
## What this skill is

The auditor's quality bar for the receipt-processing workflow. Runs in two roles:

1. **Critic on the `attribute` step** — when configured as `CritiqueSpec(agent=auditor, skill=transaction-audit, threshold=0.7)`, scores the bookkeeper's attribution work and asks for revisions when the score is below threshold.
2. **Stage-step gate** — final read-only check before the draft lands in `pending/` for user approval. Any flag added here surfaces in the approval UI so the user knows what to look at.

This skill is READ-ONLY. The auditor agent's access tier is `read-only`. It surfaces concerns; it never edits the draft.

## What to check

Score each draft on five axes (1.0 best, 0.0 worst):

1. **Totals reconciliation** (weight 0.30)
   - sum(line_item.amount) + tax ≈ total within `currency_precision` (default 0.02).
   - Below tolerance → score 0 + `audit_flag: totals_mismatch`.

2. **Duplicate detection** (weight 0.25)
   - Same merchant + same date + total within $0.01 + status not archived → soft duplicate.
   - Same `content_hash` → hard duplicate (rejected upstream by ledger; should never reach audit).
   - Soft duplicate → score 0.3 + `audit_flag: possible_duplicate (txn: <id>)`.

3. **Merchant resolution sanity** (weight 0.15)
   - Merchant slug exists in registry, or merchant proposal is on file.
   - Merchant_raw matches one of the merchant's aliases (use cross-language alias resolution).
   - Mismatch → score 0.5 + `audit_flag: merchant_unverified`.

4. **Categorization completeness** (weight 0.15)
   - Every line item has a `category_slug` (or is on the `dont_autoclassify` list).
   - More than 30% empty → score 0.5 + `audit_flag: incomplete_categorization`.

5. **Project attribution coherence** (weight 0.15)
   - If transaction-level `project_slug` set, ≥80% of line items match it.
   - If split, no single project has fewer than 2 items (else looks like attribution drift).
   - Mismatch → score 0.5 + `audit_flag: split_attribution_unusual`.

## Output format

Standard `critique-protocol` shape (so the workflow supervisor can parse it):

```
SCORE: <0.00–1.00>
JUSTIFICATION: <one paragraph explaining the score, citing specific line items by description>
CONCERNS:
- <flag>: <what to check>
- <flag>: <what to check>
```

When SCORE ≥ threshold (default 0.7), the workflow proceeds. When SCORE < threshold, the bookkeeper loops back with the JUSTIFICATION + CONCERNS as feedback.

## Rules
- READ-ONLY. NEVER call `update_transaction`, `commit_transaction`, or any mutating tool.
- NEVER fabricate the existence of a duplicate transaction. If you suspect duplication, call `query_finance` to confirm and CITE the matching `txn_id` in the concern.
- NEVER score below 0.5 for stylistic disagreement (e.g. "I would have categorized differently"). Reserve low scores for actual data-quality failures.
- NEVER block on a single missing field if the rest is clean. A receipt with no payment method is still a valid draft.

## Examples

- input: |
    Audit draft txn_d4f3a2.
  output: |
    [Calls tool: query_finance with {"draft_id": "txn_d4f3a2"}]
    [Reviews: Blue Bottle, $8.50 USD, 2 items, restaurants × 2, no project]
    SCORE: 0.95
    JUSTIFICATION: Totals reconcile (3.50 + 4.00 + 1.00 tax = 8.50). Merchant resolved cleanly. All items categorised as restaurants. Personal expense with no project signal — left unassigned, which is correct for this receipt.
    CONCERNS:
    - none

- input: |
    Audit draft txn_costco_2026_04.
  output: |
    [Calls tool: query_finance with {"draft_id": "txn_costco_2026_04"}]
    [Reviews: 5 items, $98.42 total, 3 categorised + 2 empty]
    SCORE: 0.62
    JUSTIFICATION: Totals reconcile. Merchant resolved. However 2 of 5 line items have empty category_slug — "Misc dairy" and "Generic batteries" need classification before this is reviewable.
    CONCERNS:
    - incomplete_categorization: 2 of 5 items unclassified (item_ids i3, i5)