# Fuzzy Transaction Match data contract

API: `fuzzy_transaction_match(query, internal_groups, external_groups, context)` / `fuzzyTransactionMatch(query, internalGroups, externalGroups, context)`. All arguments are JSON values; every object below has exactly the listed keys. No implicit policy defaults, coercion, amount tolerance or claim registry. Identifiers match `[A-Za-z0-9][A-Za-z0-9._:-]{0,63}`. Positive integers are canonical decimal strings of 1..30 digits without leading zero; sequence zero is allowed only at an empty source head. Civil dates are real YYYY-MM-DD; instants are real UTC YYYY-MM-DDTHH:mm:ss.sssZ. Money is positive minor units; USD and EUR supported, never converted.

## Query and policy

Query: `schema_version, ledger_id, entity_id, reconciliation_account_id, period_start, period_end, knowledge_cutoff, reference_policy, policy`. Fixed schema `d30-fuzzy-transaction-match-v1`, reference policy `BYTE_EXACT_ASCII_V1`. The booking window is [period_start, period_end), 1..366 civil days; K must be at least period_end midnight UTC and within each independently trusted source head's observed_at. Scope is declared and bound by both heads; host trust/authentication is not inferred from strings.

Policy exactly: `algorithm:token-jaccard-date-v1`, `max_day_gap` string 0..7, `threshold_points` string 0..100, `min_margin_points` string 0..100. Default teaching fixture explicitly sends W2/T80/M10. The API supplies no missing defaults. Changing W changes both the date gate and date score denominator. Weights are fixed 80 and 20 points.

## Complete source evidence

Context exactly `internal_head,external_head`. Head exactly `schema_version,source_id,side,ledger_id,entity_id,reconciliation_account_id,coverage_start,coverage_end,observed_at,head_sequence,receipt_ref`; schema `d30-reconciliation-head-v1`, side `internal`/`external`. Coverage contains the entire requested window. Head sequences describe the normalized observer log, not a native bank statement numbering rule. Empty head sequence `0` requires an empty group list.

Each group exactly `schema_version,group_id,source_id,available_at,events`; schema `d30-reconciliation-group-v1`. Each event exactly `sequence,row_id,revision,supersedes_revision,source_version_ref,action,movement`. An upsert contains one full movement; withdrawal contains movement:null. Each source has at most 4096 groups and 4096 events; each group has 1..32 events. One logical row occurs at most once per group. A group occupies a consecutive sequence interval. Delivery may permute; declared sequence canonically orders the normalized log after duplicate/conflict checks. Groups cannot interleave; canonical receipts are nondecreasing and no later than observed_at. Exact duplicate delivery rejects rather than counting twice; changed group/event identities reject.

Internal rows are immutable revision-one upserts with supersedes_revision:null. External rows start revision1 upsert/null, then increment revisions and name the exact predecessor. Withdraw only an active upsert; later upsert may reinstate. Financial reversals are separate actual movements; evidence withdrawal is not an economic undo. Full history must cover 1..head_sequence, including future-unselected groups. Validate it before K and window cuts; omitted future history cannot masquerade as complete evidence. At K, retain whole groups with available_at≤K, take each row's latest visible revision, remove known withdrawals, then filter active movements by booking date. A revision can move a row outside the window. All equal-receipt groups are inclusive.

Movement exactly `booking_status,booking_date,value_date,currency,direction,amount_minor,reference,description,is_reversal`. booking_status `booked`; direction `inflow`/`outflow` from the account owner's bank-asset perspective. A CRDT adapter maps inflow and DBIT maps outflow; a separately flagged reversal must not flip that mapped movement again. Amount positive; is_reversal exact bool. Booking date cannot exceed its group's receipt calendar date; future value dates are allowed and retained, never substituted for booking date.

Reference is null or exactly `{namespace,value}`. Namespace strict identifier; value printable ASCII, up to 140 characters. Usable strong keys retain original bytes. Null, empty, ASCII-spaces-only and exact uppercase NOTPROVIDED are missing. Lowercase notprovided is usable. Different namespaces are noncomparable. Same namespace/different usable bytes yields REFERENCE_CONFLICT; same usable bytes remain eligible. No case folding, trimming or punctuation normalization of strong references.

Description is at most 240 Unicode scalars; surrogates and C0/C1/DEL controls reject shared input. The fuzzy profile additionally requires every character U+0020..U+007E, otherwise that selected row is unsupported for fuzzy scoring. Lowercase ASCII and extract `[a-z0-9]+`, deduplicate and sort. Empty sets are not comparable, including empty versus empty. No silent transliteration or dropping unsupported script.

## Gates, score and selection

Pair gate reasons in order: CURRENCY_MISMATCH, DIRECTION_MISMATCH, AMOUNT_MISMATCH, REVERSAL_KIND_MISMATCH, REFERENCE_CONFLICT, DATE_WINDOW_MISMATCH. Scope and malformed source evidence fail globally before comparisons. Financial gates run before memo scoring. Signed booking_date_gap_days is external minus internal; date gate/formula uses absolute value. For nonempty token sets U,V, J=intersection/union; D=(W+1−abs(gap))/(W+1); S=80J+20D. Reduced fraction wires exactly `{numerator,denominator}`, signed canonical numerator and positive denominator strings. Scores are points, never probabilities.

Rank all gated/text-comparable pairs, including below-threshold rivals. A proposal needs S≥T, exactly one highest incident edge at both endpoints and top−second-counterparty≥M on both endpoints. Equal margin passes; no runner gives null margin/unopposed. Top ties always fail, even with M0. This is one pass on the original graph: no greedy consumption, lexical tie winner or iterative peeling. Not a maximum-weight assignment.

## Exact output

Always exactly `status,code,coverage,selected,proposals,comparisons,row_decisions`. Failure has coverage:null,selected:null and three empty arrays. Status rejected for strict/policy/history/resource errors; indeterminate for SOURCE_SEQUENCE_GAP, INCOMPLETE_SOURCE, KNOWLEDGE_BEYOND_SOURCE or COVERAGE_WINDOW_MISMATCH. Policy rejects INVALID_POLICY; wrong shape INVALID_SCHEMA; wrong own schema UNSUPPORTED_SCHEMA. Other typed source errors retain their selector code. No partially scored output on failure. Active selected rows exceeding 128 on either side reject FUZZY_RESOURCE_LIMIT after full shared validation. Empty valid evidence returns ok with empty arrays.

Success `status:ok,code:FUZZY_PROPOSALS_READY`. coverage exactly internal/external; each has `source_id,receipt_ref,observed_at,coverage_start,coverage_end,head_sequence,validated_through_sequence,visible_through_sequence`. Independent-head provenance may mention complete future coverage; comparisons contain only selected evidence.

selected exactly `internal,external,withdrawn,out_of_window_counts`. Active row exactly `row_ref,group_id,available_at,movement`; row_ref exactly `source_id,row_id,revision,sequence,source_version_ref`. withdrawn has internal/external arrays of `{row_ref,group_id,available_at}`; out_of_window_counts has internal/external canonical nonnegative strings. Rows are sorted by row_id then numeric revision. IDs order presentation, never choose a winner.

Proposal exactly `internal_row_ref,external_row_ref,score_points,token_intersection_count,token_union_count,booking_date_gap_days,internal_margin_points,external_margin_points,reason,status`. Reason MUTUAL_UNIQUE_BEST_WITH_MARGIN; status review-proposal. Counts JSON integers; score/margins exact fraction objects, margin nullable. Proposals never certify consumption, source identity, accepted resolution, settlement or posting.

Comparison exactly `internal_row_ref,external_row_ref,booking_date_gap_days,gate_reasons,text_status,internal_tokens,external_tokens,token_intersection_count,token_union_count,token_jaccard,date_component,score_points,threshold_pass,internal_rank,external_rank,proposed,reason`. Financial gate produces text_status:not-evaluated, null features/ranks, false threshold/proposed, reason HARD_GATE_FAILED. Unsupported or empty profile gives text_status unsupported/not-comparable and TEXT_PROFILE_UNSUPPORTED/TEXT_NOT_COMPARABLE; no score. Comparable tokens sorted arrays. Nonnull rank exactly `best_score_points,is_best,unique_best,runner_up_score_points,margin_points,margin_pass`. Rank's best and runner reflect the full original graph; an edge below the best still carries the endpoint's actual rank summary. Comparable reason precedence BELOW_THRESHOLD, NOT_MUTUAL_BEST, BEST_SCORE_TIE, INSUFFICIENT_MARGIN, then MUTUAL_UNIQUE_BEST_WITH_MARGIN.

Row decision exactly `side,row_ref,text_profile_status,tokens,best_score_points,runner_up_score_points,margin_points,decision,reason,proposed_counterpart_ref`. Every active selected row appears once. Decision proposed/unsupported-text/text-not-comparable/no-eligible-candidate/below-threshold/review-ambiguous. Unsupported tokens:null; empty tokens:[]. Counterpart ref only if proposed. Ambiguity uses tie reason, or the unique best edge's actual failed reason. Proposal/comparison order internal row_id then external row_id; row decisions internal first then external. Output views are detached from inputs and other mutable views.

Host boundary: all selected rows are evaluated. One-row-once holds within this proposal run. A trusted accepted-resolution registry must veto/review reuse or revised evidence before any durable acceptance; it is deliberately not a fifth input. Never omit history to emulate exclusions. No database write, concurrent reservation or journal commit is implemented.
