When a document the auto-classifier flags as an "invoice" finishes ingest, Kodori automatically:
1. Reads the extracted text and asks Haiku for the structured fields it can ground there: vendor name, invoice number, total amount, currency, PO number. 2. If a PO number was found, scans the workspace for a document whose display name contains that PO and links it ("✓ PO doc Foo-1234"). Single-match wins; ambiguity falls through to "no match". 3. **Three-way match.** With the PO match in hand, Kodori looks for a receipt (packing slip / goods-received note / delivery receipt) that shares the same PO number. Found, with totals, → match status is **3-way matched** (variance within $5 / 1% tolerance) or **price-variance** (totals disagree beyond tolerance). PO matched but no receipt yet → **awaiting receipt**. No PO doc → **2-way only** (the invoice is still reviewable, just not three-way matched). 4. Inserts a row into the AP review queue at /ap-review with status "pending" (or "extraction-failed" if the extractor couldn't read vendor + total).
**Late-arriving receipts retroactively reconcile.** Receipts often land after the invoice — the AP clerk files the invoice immediately, the receiver scans the packing slip a day later. When a receipt is filed, Kodori sweeps every existing invoice for the same PO and updates their match status in place; you don't need to "kick" stale invoice rows.
A workspace member then opens /ap-review and decides:
- **Approve** — flips status to "approved" and emits an ap-invoice.approved event with vendor + total + currency + match status. Webhook subscribers receive the event for downstream ERP sync. - **Reject** — captures a free-text reason ("duplicate of 2024-1042", "vendor disputes total"); event payload preserves it.
The flow doesn't bypass governance: the same permission checks fire when the agent is involved, and the approval / rejection events feed the same hash-chained audit log as everything else. If a held-doc deny-wins refusal triggers, the queue surfaces it.
**Receipts as documents.** Receipts are filed like any other document — drag-drop onto /upload, mobile capture from /capture, or email-to-DMS. The classifier recognizes the standard vocabulary (receipt, packing slip, goods received note, delivery receipt) and the receipt extractor pulls vendor / PO / total. Receipts that don't print a document-level total (common on partial-delivery slips) still file successfully — the match status holds at "pending" until you reconcile line items by hand or upload a fuller version.
**Tolerance.** A variance within max($5, 1%) lands matched. The thresholds live in the workflow code today; per-tenant tolerance overrides are a future enhancement. The signed variance (positive when invoice > receipt; negative when invoice < receipt) is captured on the row so a buyer can see both magnitude and direction.
**Line-item match.** When invoice + matched receipt both have line items captured, the /ap-review row offers a "View line items" expander that pairs lines side-by-side. Pairing tries item code first (the strongest signal vendors print), then exact description match, then line number. Each pair shows ✓ matched (totals within $1), ! variant (totals diverge), or unpaired (one side has the line, the other doesn't). Header: "5 matched · 2 variant · 1 unpaired" — surfaces the line-level posture even when document-level totals happen to reconcile (a vendor who billed twice for one item but waived another may show net-zero variance at the document level but two line-level variants).
This is the "first end-to-end workflow" Phase-1 exit criterion: ingest → IDP extraction → auto-classify → field extraction → three-way match → line-item reconcile → human approval → audit.