# Stage 19 native document matching — 2026-09-09

Later implementation and release preparation are recorded in
[matching completion](stage19-matching-completion-260909.md). The checkpoints below retain their
original scope and validation history.

Development checkpoint on `c054b7c`, extending the uncommitted fiscal and bank-statement
persistence work. No production deployment or migration is part of this change.

Current native shapes: receipt-date, FIFO, and exact payable/receivable proposals. The second
checkpoint below extends the first checkpoint's implementation and validation record.

## Implemented

`DocumentMatchLadder` evaluates Receipt-Dated Document Match and FIFO Order Assumption from
the owner's live BNKMAT declarations. Receipt matching requires an exact amount in the same
currency, the same resolved counterparty and company, a positive payable entry, and a document
date on or after the movement inside the declared window. It uses normalized CashDelta, so a
credit-card statement's opposite native sign does not reverse the payment direction. Provisional
amount-only identity is insufficient. Missing or contradictory rule parameters never get defaults.

The runner-up must be separated from the nearest document by at least AmbiguityMarginDays.
Ties and competing movements claiming the same document produce explicit review holds. Declared
priority orders candidates; receipt evidence outranks FIFO, including when a numeric inversion is
reported. FIFO remains a labelled assumption. Ambiguous receipt matching can fall through to a
valid later shape, with its refusal retained in the proposal evidence.

`DocumentMatchingService` runs after successful fiscal or bank-statement persistence in the
document worker, including receipt replays. It resolves counterparty identity through the existing
ladder and creates or refreshes a RECREV Document Match item with a structured EntryCode proposal
and BNKMAT identity. It evaluates all eligible movements before claiming candidates, serializes
company runs, and locks movement rows against simultaneous application of a human decision.

These are review proposals, not automatic settlement entries. A person applies a decision through
the existing review engine. Applied matches carry the matching rule when the person accepts the
proposed document. The apply path now rechecks protected/hammered authority and rejects a stale
whole-document proposal when another reconciliation or payment event has claimed its document.

Existing bindings, protected matches, hammered rows, pending human decisions, deleted or mirrored
rows, and inaccessible companies remain excluded. Completed and partial settlement evidence excludes
a document from these whole-document shapes. Rejected pairings remain constraints; fresh evidence
can raise a new review generation without changing the old ballot or observations.

## Scope and remaining work

This is a native matching checkpoint, **not full parity with every source matcher**. Other BNKMAT
shapes are reported as having no native evaluator. In particular, imported payment/receivable
reference sheets are not reconstructed from the derived PAYCTR/RCVCTR tables. FX/grouped,
gateway, balance, internal-transfer and reference-sheet evaluators remain separate work. No existing
match is automatically rebound, and AutoCloseAllowed does not silently enable automatic closure.

The source anchors are `Scripts/match_bnkmov_receipt_date.py`, `Scripts/check_match_priority.py`
and the receipt/FIFO BNKMAT declarations in `invoice-skill-build` at the previously reviewed
`616f10b`. No reference documents or sealed acceptance inputs are required by these tests.

The existing C6 owner guard remains in force. No BNKMAT rows are invented or installed for a real
owner. Missing configuration creates a review hold. Main/Preview parity and fresh sealed acceptance
remain release gates; this checkpoint adds no schema changes.

## Validation

Synthetic unit and Preview database tests cover date/margin boundaries, exact amounts, currency and
company isolation, credit-card signs, provisional identity, missing parameters, competing movements,
replays, concurrent runs, changed evidence, permanent rejection constraints and protected human
decisions. The existing worker integration test exercises the new post-persistence dispatch.

Final per-suite validation: **1,162 passed / 3 failed / 0 skipped**. Argus **339**, Platform **552**,
API **56**, Browser **160**, and TenantIsolation **55 passed / 3 failed**. Only the existing
Main/Preview migration, table and AI-reader grant parity gates failed. The final Argus run includes
all 40 new matching cases; the final complete Browser run has Playwright enabled.

The first Playwright-enabled run found that four browser scripts sent `en-GB`, although the
application's declared English culture is `en-US`. Five interaction tests therefore rendered
Portuguese. The scripts now use the supported culture; English/Portuguese and desktop/mobile
assertions remain intact. Two harness timing tests also failed on that run and passed on the final
complete Browser run without harness changes or relaxed assertions. These were separate suite runs,
not one all-green solution invocation.

Evidence: `local/artifacts/tests/stage19-matching/`, particularly
`stage19-matching-argus-verified.trx`, `stage19-matching-browser-final.trx`, and the timestamped
`stage19-matching-final_net10.0_*.trx` files for API, Platform and TenantIsolation. Earlier failed
runs are retained. Test teardown now removes the new BNKMAT and RECREV fixtures in dependency order;
the five fixtures left by the initial teardown failures were explicitly removed from Preview.
`git diff --check` passed and the source `invoice-skill-build` checkout remained clean.

## Second checkpoint — exact payable and receivable matching

Exact Document Match now participates in the native ladder. It requires a known counterparty,
same owner/company/currency, positive document total, and the correct settlement side: outflow
for payables, inflow for receivables. The date window is symmetric and inclusive; exactly one
eligible document is required. A closer date does not break a tie between two eligible documents.
Receipt and FIFO remain payable-only shapes.

Window and tolerance must be numeric BNKMAT declarations. The source's compound window text
(`10 standard; 45 exact-wide; 90 vendor-scoped`) is not silently reduced to one number. Exact
matching uses the source amount/date pass's strict positive-tolerance boundary; a declared zero
requires exact equality. It never adds a rounding allowance or converts currencies. This is a
conservative same-counterparty native subset of `match_bnkmov_reconciliation.py`, not its
vendor-specific exceptions, payroll generation or reference-substring passes.

Claim allocation now considers declared priority across movements too: a unique stronger claim
reserves the document, while equal-strength claims are all held. No weaker claim silently gets an
alternative document on that pass. No existing binding is automatically replaced.

RCVDTL and RCVCTR now receive the same whole-document exclusions as their payable counterparts.
The human apply path rechecks both settlement ledgers and both control tables for partial payments,
settled amounts and holds. Rejected constraints identified only by BMCode are also honored before
proposal and application. Existing human decisions and source/cutover boundaries remain intact.

This checkpoint changes no schema or owner configuration. Full FX, grouped, internal-transfer,
gateway and imported reference-sheet strategies remain open. Fresh production acceptance and the
existing Main/Preview parity gates are still release requirements.

Validation: the complete Argus suite passed **363/363**, zero failures/skips, including 24 new
cases for this checkpoint. Evidence: `local/artifacts/tests/stage19-matching/exact-matching-argus-final.trx`.
The initial 361-test run also passed; the final run adds strict-tolerance boundary cases and
strengthens the partial-receipt exclusion test. `git diff --check` passed. Other suites and the
known parity failures were not rerun for this backend-only extension; their earlier results above
are historical, not a claim of a fresh whole-solution run. The source checkout remains clean.
